Clear Engine-Output pane when initializing engine
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237
238 #ifdef WIN32
239        extern void ConsoleCreate();
240 #endif
241
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
245
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 Boolean abortMatch;
254
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 int endPV = -1;
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
262 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
266 Boolean partnerUp;
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
278 int chattingPartner;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
284
285 /* States for ics_getting_history */
286 #define H_FALSE 0
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
292
293 /* whosays values for GameEnds */
294 #define GE_ICS 0
295 #define GE_ENGINE 1
296 #define GE_PLAYER 2
297 #define GE_FILE 3
298 #define GE_XBOARD 4
299 #define GE_ENGINE1 5
300 #define GE_ENGINE2 6
301
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
304
305 /* Different types of move when calling RegisterMove */
306 #define CMAIL_MOVE   0
307 #define CMAIL_RESIGN 1
308 #define CMAIL_DRAW   2
309 #define CMAIL_ACCEPT 3
310
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
315
316 /* Telnet protocol constants */
317 #define TN_WILL 0373
318 #define TN_WONT 0374
319 #define TN_DO   0375
320 #define TN_DONT 0376
321 #define TN_IAC  0377
322 #define TN_ECHO 0001
323 #define TN_SGA  0003
324 #define TN_PORT 23
325
326 char*
327 safeStrCpy( char *dst, const char *src, size_t count )
328 { // [HGM] made safe
329   int i;
330   assert( dst != NULL );
331   assert( src != NULL );
332   assert( count > 0 );
333
334   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335   if(  i == count && dst[count-1] != NULLCHAR)
336     {
337       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338       if(appData.debugMode)
339       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
340     }
341
342   return dst;
343 }
344
345 /* Some compiler can't cast u64 to double
346  * This function do the job for us:
347
348  * We use the highest bit for cast, this only
349  * works if the highest bit is not
350  * in use (This should not happen)
351  *
352  * We used this for all compiler
353  */
354 double
355 u64ToDouble(u64 value)
356 {
357   double r;
358   u64 tmp = value & u64Const(0x7fffffffffffffff);
359   r = (double)(s64)tmp;
360   if (value & u64Const(0x8000000000000000))
361        r +=  9.2233720368547758080e18; /* 2^63 */
362  return r;
363 }
364
365 /* Fake up flags for now, as we aren't keeping track of castling
366    availability yet. [HGM] Change of logic: the flag now only
367    indicates the type of castlings allowed by the rule of the game.
368    The actual rights themselves are maintained in the array
369    castlingRights, as part of the game history, and are not probed
370    by this function.
371  */
372 int
373 PosFlags(index)
374 {
375   int flags = F_ALL_CASTLE_OK;
376   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377   switch (gameInfo.variant) {
378   case VariantSuicide:
379     flags &= ~F_ALL_CASTLE_OK;
380   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381     flags |= F_IGNORE_CHECK;
382   case VariantLosers:
383     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384     break;
385   case VariantAtomic:
386     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387     break;
388   case VariantKriegspiel:
389     flags |= F_KRIEGSPIEL_CAPTURE;
390     break;
391   case VariantCapaRandom:
392   case VariantFischeRandom:
393     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394   case VariantNoCastle:
395   case VariantShatranj:
396   case VariantCourier:
397   case VariantMakruk:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 int shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating(str)
650   char *str;
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine(ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions(ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740 "first",
741 "second"
742 };
743
744 void
745 InitEngine(ChessProgramState *cps, int n)
746 {   // [HGM] all engine initialiation put in a function that does one engine
747
748     ClearOptions(cps);
749
750     cps->which = engineNames[n];
751     cps->maybeThinking = FALSE;
752     cps->pr = NoProc;
753     cps->isr = NULL;
754     cps->sendTime = 2;
755     cps->sendDrawOffers = 1;
756
757     cps->program = appData.chessProgram[n];
758     cps->host = appData.host[n];
759     cps->dir = appData.directory[n];
760     cps->initString = appData.engInitString[n];
761     cps->computerString = appData.computerString[n];
762     cps->useSigint  = TRUE;
763     cps->useSigterm = TRUE;
764     cps->reuse = appData.reuse[n];
765     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
766     cps->useSetboard = FALSE;
767     cps->useSAN = FALSE;
768     cps->usePing = FALSE;
769     cps->lastPing = 0;
770     cps->lastPong = 0;
771     cps->usePlayother = FALSE;
772     cps->useColors = TRUE;
773     cps->useUsermove = FALSE;
774     cps->sendICS = FALSE;
775     cps->sendName = appData.icsActive;
776     cps->sdKludge = FALSE;
777     cps->stKludge = FALSE;
778     TidyProgramName(cps->program, cps->host, cps->tidy);
779     cps->matchWins = 0;
780     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781     cps->analysisSupport = 2; /* detect */
782     cps->analyzing = FALSE;
783     cps->initDone = FALSE;
784
785     /* New features added by Tord: */
786     cps->useFEN960 = FALSE;
787     cps->useOOCastle = TRUE;
788     /* End of new features added by Tord. */
789     cps->fenOverride  = appData.fenOverride[n];
790
791     /* [HGM] time odds: set factor for each machine */
792     cps->timeOdds  = appData.timeOdds[n];
793
794     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795     cps->accumulateTC = appData.accumulateTC[n];
796     cps->maxNrOfSessions = 1;
797
798     /* [HGM] debug */
799     cps->debug = FALSE;
800
801     cps->supportsNPS = UNKNOWN;
802     cps->memSize = FALSE;
803     cps->maxCores = FALSE;
804     cps->egtFormats[0] = NULLCHAR;
805
806     /* [HGM] options */
807     cps->optionSettings  = appData.engOptions[n];
808
809     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810     cps->isUCI = appData.isUCI[n]; /* [AS] */
811     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
812
813     if (appData.protocolVersion[n] > PROTOVER
814         || appData.protocolVersion[n] < 1)
815       {
816         char buf[MSG_SIZ];
817         int len;
818
819         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820                        appData.protocolVersion[n]);
821         if( (len > MSG_SIZ) && appData.debugMode )
822           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
823
824         DisplayFatalError(buf, 0, 2);
825       }
826     else
827       {
828         cps->protocolVersion = appData.protocolVersion[n];
829       }
830
831     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
832 }
833
834 ChessProgramState *savCps;
835
836 void
837 LoadEngine()
838 {
839     int i;
840     if(WaitForEngine(savCps, LoadEngine)) return;
841     CommonEngineInit(); // recalculate time odds
842     if(gameInfo.variant != StringToVariant(appData.variant)) {
843         // we changed variant when loading the engine; this forces us to reset
844         Reset(TRUE, savCps != &first);
845         EditGameEvent(); // for consistency with other path, as Reset changes mode
846     }
847     InitChessProgram(savCps, FALSE);
848     SendToProgram("force\n", savCps);
849     DisplayMessage("", "");
850     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852     ThawUI();
853     SetGNUMode();
854 }
855
856 void
857 ReplaceEngine(ChessProgramState *cps, int n)
858 {
859     EditGameEvent();
860     UnloadEngine(cps);
861     appData.noChessProgram = FALSE;
862     appData.clockMode = TRUE;
863     InitEngine(cps, n);
864     if(n) return; // only startup first engine immediately; second can wait
865     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
866     LoadEngine();
867 }
868
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871
872 static char resetOptions[] = 
873         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900     if(params[0]) {
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
974
975     ClearProgramStats();
976     programStats.ok_to_send = 1;
977     programStats.seen_stat = 0;
978
979     /*
980      * Initialize game list
981      */
982     ListNew(&gameList);
983
984
985     /*
986      * Internet chess server status
987      */
988     if (appData.icsActive) {
989         appData.matchMode = FALSE;
990         appData.matchGames = 0;
991 #if ZIPPY
992         appData.noChessProgram = !appData.zippyPlay;
993 #else
994         appData.zippyPlay = FALSE;
995         appData.zippyTalk = FALSE;
996         appData.noChessProgram = TRUE;
997 #endif
998         if (*appData.icsHelper != NULLCHAR) {
999             appData.useTelnet = TRUE;
1000             appData.telnetProgram = appData.icsHelper;
1001         }
1002     } else {
1003         appData.zippyTalk = appData.zippyPlay = FALSE;
1004     }
1005
1006     /* [AS] Initialize pv info list [HGM] and game state */
1007     {
1008         int i, j;
1009
1010         for( i=0; i<=framePtr; i++ ) {
1011             pvInfoList[i].depth = -1;
1012             boards[i][EP_STATUS] = EP_NONE;
1013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014         }
1015     }
1016
1017     InitTimeControls();
1018
1019     /* [AS] Adjudication threshold */
1020     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1021
1022     InitEngine(&first, 0);
1023     InitEngine(&second, 1);
1024     CommonEngineInit();
1025
1026     pairing.which = "pairing"; // pairing engine
1027     pairing.pr = NoProc;
1028     pairing.isr = NULL;
1029     pairing.program = appData.pairingEngine;
1030     pairing.host = "localhost";
1031     pairing.dir = ".";
1032
1033     if (appData.icsActive) {
1034         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1035     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036         appData.clockMode = FALSE;
1037         first.sendTime = second.sendTime = 0;
1038     }
1039
1040 #if ZIPPY
1041     /* Override some settings from environment variables, for backward
1042        compatibility.  Unfortunately it's not feasible to have the env
1043        vars just set defaults, at least in xboard.  Ugh.
1044     */
1045     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046       ZippyInit();
1047     }
1048 #endif
1049
1050     if (!appData.icsActive) {
1051       char buf[MSG_SIZ];
1052       int len;
1053
1054       /* Check for variants that are supported only in ICS mode,
1055          or not at all.  Some that are accepted here nevertheless
1056          have bugs; see comments below.
1057       */
1058       VariantClass variant = StringToVariant(appData.variant);
1059       switch (variant) {
1060       case VariantBughouse:     /* need four players and two boards */
1061       case VariantKriegspiel:   /* need to hide pieces and move details */
1062         /* case VariantFischeRandom: (Fabien: moved below) */
1063         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064         if( (len > MSG_SIZ) && appData.debugMode )
1065           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1066
1067         DisplayFatalError(buf, 0, 2);
1068         return;
1069
1070       case VariantUnknown:
1071       case VariantLoadable:
1072       case Variant29:
1073       case Variant30:
1074       case Variant31:
1075       case Variant32:
1076       case Variant33:
1077       case Variant34:
1078       case Variant35:
1079       case Variant36:
1080       default:
1081         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082         if( (len > MSG_SIZ) && appData.debugMode )
1083           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1084
1085         DisplayFatalError(buf, 0, 2);
1086         return;
1087
1088       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1089       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1090       case VariantGothic:     /* [HGM] should work */
1091       case VariantCapablanca: /* [HGM] should work */
1092       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1093       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1094       case VariantKnightmate: /* [HGM] should work */
1095       case VariantCylinder:   /* [HGM] untested */
1096       case VariantFalcon:     /* [HGM] untested */
1097       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098                                  offboard interposition not understood */
1099       case VariantNormal:     /* definitely works! */
1100       case VariantWildCastle: /* pieces not automatically shuffled */
1101       case VariantNoCastle:   /* pieces not automatically shuffled */
1102       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103       case VariantLosers:     /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantSuicide:    /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantGiveaway:   /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantTwoKings:   /* should work */
1110       case VariantAtomic:     /* should work except for win condition */
1111       case Variant3Check:     /* should work except for win condition */
1112       case VariantShatranj:   /* should work except for all win conditions */
1113       case VariantMakruk:     /* should work except for daw countdown */
1114       case VariantBerolina:   /* might work if TestLegality is off */
1115       case VariantCapaRandom: /* should work */
1116       case VariantJanus:      /* should work */
1117       case VariantSuper:      /* experimental */
1118       case VariantGreat:      /* experimental, requires legality testing to be off */
1119       case VariantSChess:     /* S-Chess, should work */
1120       case VariantSpartan:    /* should work */
1121         break;
1122       }
1123     }
1124
1125 }
1126
1127 int NextIntegerFromString( char ** str, long * value )
1128 {
1129     int result = -1;
1130     char * s = *str;
1131
1132     while( *s == ' ' || *s == '\t' ) {
1133         s++;
1134     }
1135
1136     *value = 0;
1137
1138     if( *s >= '0' && *s <= '9' ) {
1139         while( *s >= '0' && *s <= '9' ) {
1140             *value = *value * 10 + (*s - '0');
1141             s++;
1142         }
1143
1144         result = 0;
1145     }
1146
1147     *str = s;
1148
1149     return result;
1150 }
1151
1152 int NextTimeControlFromString( char ** str, long * value )
1153 {
1154     long temp;
1155     int result = NextIntegerFromString( str, &temp );
1156
1157     if( result == 0 ) {
1158         *value = temp * 60; /* Minutes */
1159         if( **str == ':' ) {
1160             (*str)++;
1161             result = NextIntegerFromString( str, &temp );
1162             *value += temp; /* Seconds */
1163         }
1164     }
1165
1166     return result;
1167 }
1168
1169 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1170 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1171     int result = -1, type = 0; long temp, temp2;
1172
1173     if(**str != ':') return -1; // old params remain in force!
1174     (*str)++;
1175     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1176     if( NextIntegerFromString( str, &temp ) ) return -1;
1177     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1178
1179     if(**str != '/') {
1180         /* time only: incremental or sudden-death time control */
1181         if(**str == '+') { /* increment follows; read it */
1182             (*str)++;
1183             if(**str == '!') type = *(*str)++; // Bronstein TC
1184             if(result = NextIntegerFromString( str, &temp2)) return -1;
1185             *inc = temp2 * 1000;
1186             if(**str == '.') { // read fraction of increment
1187                 char *start = ++(*str);
1188                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1189                 temp2 *= 1000;
1190                 while(start++ < *str) temp2 /= 10;
1191                 *inc += temp2;
1192             }
1193         } else *inc = 0;
1194         *moves = 0; *tc = temp * 1000; *incType = type;
1195         return 0;
1196     }
1197
1198     (*str)++; /* classical time control */
1199     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1200
1201     if(result == 0) {
1202         *moves = temp;
1203         *tc    = temp2 * 1000;
1204         *inc   = 0;
1205         *incType = type;
1206     }
1207     return result;
1208 }
1209
1210 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1211 {   /* [HGM] get time to add from the multi-session time-control string */
1212     int incType, moves=1; /* kludge to force reading of first session */
1213     long time, increment;
1214     char *s = tcString;
1215
1216     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1217     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1218     do {
1219         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1220         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1221         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1222         if(movenr == -1) return time;    /* last move before new session     */
1223         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1224         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1225         if(!moves) return increment;     /* current session is incremental   */
1226         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1227     } while(movenr >= -1);               /* try again for next session       */
1228
1229     return 0; // no new time quota on this move
1230 }
1231
1232 int
1233 ParseTimeControl(tc, ti, mps)
1234      char *tc;
1235      float ti;
1236      int mps;
1237 {
1238   long tc1;
1239   long tc2;
1240   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1241   int min, sec=0;
1242
1243   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1244   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1245       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1246   if(ti > 0) {
1247
1248     if(mps)
1249       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1250     else 
1251       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1252   } else {
1253     if(mps)
1254       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1255     else 
1256       snprintf(buf, MSG_SIZ, ":%s", mytc);
1257   }
1258   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1259   
1260   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1261     return FALSE;
1262   }
1263
1264   if( *tc == '/' ) {
1265     /* Parse second time control */
1266     tc++;
1267
1268     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1269       return FALSE;
1270     }
1271
1272     if( tc2 == 0 ) {
1273       return FALSE;
1274     }
1275
1276     timeControl_2 = tc2 * 1000;
1277   }
1278   else {
1279     timeControl_2 = 0;
1280   }
1281
1282   if( tc1 == 0 ) {
1283     return FALSE;
1284   }
1285
1286   timeControl = tc1 * 1000;
1287
1288   if (ti >= 0) {
1289     timeIncrement = ti * 1000;  /* convert to ms */
1290     movesPerSession = 0;
1291   } else {
1292     timeIncrement = 0;
1293     movesPerSession = mps;
1294   }
1295   return TRUE;
1296 }
1297
1298 void
1299 InitBackEnd2()
1300 {
1301     if (appData.debugMode) {
1302         fprintf(debugFP, "%s\n", programVersion);
1303     }
1304
1305     set_cont_sequence(appData.wrapContSeq);
1306     if (appData.matchGames > 0) {
1307         appData.matchMode = TRUE;
1308     } else if (appData.matchMode) {
1309         appData.matchGames = 1;
1310     }
1311     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1312         appData.matchGames = appData.sameColorGames;
1313     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1314         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1315         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1316     }
1317     Reset(TRUE, FALSE);
1318     if (appData.noChessProgram || first.protocolVersion == 1) {
1319       InitBackEnd3();
1320     } else {
1321       /* kludge: allow timeout for initial "feature" commands */
1322       FreezeUI();
1323       DisplayMessage("", _("Starting chess program"));
1324       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1325     }
1326 }
1327
1328 int
1329 CalculateIndex(int index, int gameNr)
1330 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1331     int res;
1332     if(index > 0) return index; // fixed nmber
1333     if(index == 0) return 1;
1334     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1335     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1336     return res;
1337 }
1338
1339 int
1340 LoadGameOrPosition(int gameNr)
1341 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1342     if (*appData.loadGameFile != NULLCHAR) {
1343         if (!LoadGameFromFile(appData.loadGameFile,
1344                 CalculateIndex(appData.loadGameIndex, gameNr),
1345                               appData.loadGameFile, FALSE)) {
1346             DisplayFatalError(_("Bad game file"), 0, 1);
1347             return 0;
1348         }
1349     } else if (*appData.loadPositionFile != NULLCHAR) {
1350         if (!LoadPositionFromFile(appData.loadPositionFile,
1351                 CalculateIndex(appData.loadPositionIndex, gameNr),
1352                                   appData.loadPositionFile)) {
1353             DisplayFatalError(_("Bad position file"), 0, 1);
1354             return 0;
1355         }
1356     }
1357     return 1;
1358 }
1359
1360 void
1361 ReserveGame(int gameNr, char resChar)
1362 {
1363     FILE *tf = fopen(appData.tourneyFile, "r+");
1364     char *p, *q, c, buf[MSG_SIZ];
1365     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1366     safeStrCpy(buf, lastMsg, MSG_SIZ);
1367     DisplayMessage(_("Pick new game"), "");
1368     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1369     ParseArgsFromFile(tf);
1370     p = q = appData.results;
1371     if(appData.debugMode) {
1372       char *r = appData.participants;
1373       fprintf(debugFP, "results = '%s'\n", p);
1374       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1375       fprintf(debugFP, "\n");
1376     }
1377     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1378     nextGame = q - p;
1379     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1380     safeStrCpy(q, p, strlen(p) + 2);
1381     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1382     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1383     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1384         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1385         q[nextGame] = '*';
1386     }
1387     fseek(tf, -(strlen(p)+4), SEEK_END);
1388     c = fgetc(tf);
1389     if(c != '"') // depending on DOS or Unix line endings we can be one off
1390          fseek(tf, -(strlen(p)+2), SEEK_END);
1391     else fseek(tf, -(strlen(p)+3), SEEK_END);
1392     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1393     DisplayMessage(buf, "");
1394     free(p); appData.results = q;
1395     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1396        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1397         UnloadEngine(&first);  // next game belongs to other pairing;
1398         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1399     }
1400 }
1401
1402 void
1403 MatchEvent(int mode)
1404 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1405         int dummy;
1406         if(matchMode) { // already in match mode: switch it off
1407             abortMatch = TRUE;
1408             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1409             return;
1410         }
1411 //      if(gameMode != BeginningOfGame) {
1412 //          DisplayError(_("You can only start a match from the initial position."), 0);
1413 //          return;
1414 //      }
1415         abortMatch = FALSE;
1416         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1417         /* Set up machine vs. machine match */
1418         nextGame = 0;
1419         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1420         if(appData.tourneyFile[0]) {
1421             ReserveGame(-1, 0);
1422             if(nextGame > appData.matchGames) {
1423                 char buf[MSG_SIZ];
1424                 if(strchr(appData.results, '*') == NULL) {
1425                     FILE *f;
1426                     appData.tourneyCycles++;
1427                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1428                         fclose(f);
1429                         NextTourneyGame(-1, &dummy);
1430                         ReserveGame(-1, 0);
1431                         if(nextGame <= appData.matchGames) {
1432                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1433                             matchMode = mode;
1434                             ScheduleDelayedEvent(NextMatchGame, 10000);
1435                             return;
1436                         }
1437                     }
1438                 }
1439                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1440                 DisplayError(buf, 0);
1441                 appData.tourneyFile[0] = 0;
1442                 return;
1443             }
1444         } else
1445         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1446             DisplayFatalError(_("Can't have a match with no chess programs"),
1447                               0, 2);
1448             return;
1449         }
1450         matchMode = mode;
1451         matchGame = roundNr = 1;
1452         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1453         NextMatchGame();
1454 }
1455
1456 void
1457 InitBackEnd3 P((void))
1458 {
1459     GameMode initialMode;
1460     char buf[MSG_SIZ];
1461     int err, len;
1462
1463     InitChessProgram(&first, startedFromSetupPosition);
1464
1465     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1466         free(programVersion);
1467         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1468         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1469     }
1470
1471     if (appData.icsActive) {
1472 #ifdef WIN32
1473         /* [DM] Make a console window if needed [HGM] merged ifs */
1474         ConsoleCreate();
1475 #endif
1476         err = establish();
1477         if (err != 0)
1478           {
1479             if (*appData.icsCommPort != NULLCHAR)
1480               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1481                              appData.icsCommPort);
1482             else
1483               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1484                         appData.icsHost, appData.icsPort);
1485
1486             if( (len > MSG_SIZ) && appData.debugMode )
1487               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1488
1489             DisplayFatalError(buf, err, 1);
1490             return;
1491         }
1492         SetICSMode();
1493         telnetISR =
1494           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1495         fromUserISR =
1496           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1497         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1498             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1499     } else if (appData.noChessProgram) {
1500         SetNCPMode();
1501     } else {
1502         SetGNUMode();
1503     }
1504
1505     if (*appData.cmailGameName != NULLCHAR) {
1506         SetCmailMode();
1507         OpenLoopback(&cmailPR);
1508         cmailISR =
1509           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1510     }
1511
1512     ThawUI();
1513     DisplayMessage("", "");
1514     if (StrCaseCmp(appData.initialMode, "") == 0) {
1515       initialMode = BeginningOfGame;
1516       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1517         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1518         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1519         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1520         ModeHighlight();
1521       }
1522     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1523       initialMode = TwoMachinesPlay;
1524     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1525       initialMode = AnalyzeFile;
1526     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1527       initialMode = AnalyzeMode;
1528     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1529       initialMode = MachinePlaysWhite;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1531       initialMode = MachinePlaysBlack;
1532     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1533       initialMode = EditGame;
1534     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1535       initialMode = EditPosition;
1536     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1537       initialMode = Training;
1538     } else {
1539       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1540       if( (len > MSG_SIZ) && appData.debugMode )
1541         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1542
1543       DisplayFatalError(buf, 0, 2);
1544       return;
1545     }
1546
1547     if (appData.matchMode) {
1548         if(appData.tourneyFile[0]) { // start tourney from command line
1549             FILE *f;
1550             if(f = fopen(appData.tourneyFile, "r")) {
1551                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1552                 fclose(f);
1553                 appData.clockMode = TRUE;
1554                 SetGNUMode();
1555             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1556         }
1557         MatchEvent(TRUE);
1558     } else if (*appData.cmailGameName != NULLCHAR) {
1559         /* Set up cmail mode */
1560         ReloadCmailMsgEvent(TRUE);
1561     } else {
1562         /* Set up other modes */
1563         if (initialMode == AnalyzeFile) {
1564           if (*appData.loadGameFile == NULLCHAR) {
1565             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1566             return;
1567           }
1568         }
1569         if (*appData.loadGameFile != NULLCHAR) {
1570             (void) LoadGameFromFile(appData.loadGameFile,
1571                                     appData.loadGameIndex,
1572                                     appData.loadGameFile, TRUE);
1573         } else if (*appData.loadPositionFile != NULLCHAR) {
1574             (void) LoadPositionFromFile(appData.loadPositionFile,
1575                                         appData.loadPositionIndex,
1576                                         appData.loadPositionFile);
1577             /* [HGM] try to make self-starting even after FEN load */
1578             /* to allow automatic setup of fairy variants with wtm */
1579             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1580                 gameMode = BeginningOfGame;
1581                 setboardSpoiledMachineBlack = 1;
1582             }
1583             /* [HGM] loadPos: make that every new game uses the setup */
1584             /* from file as long as we do not switch variant          */
1585             if(!blackPlaysFirst) {
1586                 startedFromPositionFile = TRUE;
1587                 CopyBoard(filePosition, boards[0]);
1588             }
1589         }
1590         if (initialMode == AnalyzeMode) {
1591           if (appData.noChessProgram) {
1592             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1593             return;
1594           }
1595           if (appData.icsActive) {
1596             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1597             return;
1598           }
1599           AnalyzeModeEvent();
1600         } else if (initialMode == AnalyzeFile) {
1601           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1602           ShowThinkingEvent();
1603           AnalyzeFileEvent();
1604           AnalysisPeriodicEvent(1);
1605         } else if (initialMode == MachinePlaysWhite) {
1606           if (appData.noChessProgram) {
1607             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1608                               0, 2);
1609             return;
1610           }
1611           if (appData.icsActive) {
1612             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1613                               0, 2);
1614             return;
1615           }
1616           MachineWhiteEvent();
1617         } else if (initialMode == MachinePlaysBlack) {
1618           if (appData.noChessProgram) {
1619             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1620                               0, 2);
1621             return;
1622           }
1623           if (appData.icsActive) {
1624             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1625                               0, 2);
1626             return;
1627           }
1628           MachineBlackEvent();
1629         } else if (initialMode == TwoMachinesPlay) {
1630           if (appData.noChessProgram) {
1631             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1632                               0, 2);
1633             return;
1634           }
1635           if (appData.icsActive) {
1636             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1637                               0, 2);
1638             return;
1639           }
1640           TwoMachinesEvent();
1641         } else if (initialMode == EditGame) {
1642           EditGameEvent();
1643         } else if (initialMode == EditPosition) {
1644           EditPositionEvent();
1645         } else if (initialMode == Training) {
1646           if (*appData.loadGameFile == NULLCHAR) {
1647             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1648             return;
1649           }
1650           TrainingEvent();
1651         }
1652     }
1653 }
1654
1655 /*
1656  * Establish will establish a contact to a remote host.port.
1657  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1658  *  used to talk to the host.
1659  * Returns 0 if okay, error code if not.
1660  */
1661 int
1662 establish()
1663 {
1664     char buf[MSG_SIZ];
1665
1666     if (*appData.icsCommPort != NULLCHAR) {
1667         /* Talk to the host through a serial comm port */
1668         return OpenCommPort(appData.icsCommPort, &icsPR);
1669
1670     } else if (*appData.gateway != NULLCHAR) {
1671         if (*appData.remoteShell == NULLCHAR) {
1672             /* Use the rcmd protocol to run telnet program on a gateway host */
1673             snprintf(buf, sizeof(buf), "%s %s %s",
1674                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1675             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1676
1677         } else {
1678             /* Use the rsh program to run telnet program on a gateway host */
1679             if (*appData.remoteUser == NULLCHAR) {
1680                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1681                         appData.gateway, appData.telnetProgram,
1682                         appData.icsHost, appData.icsPort);
1683             } else {
1684                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1685                         appData.remoteShell, appData.gateway,
1686                         appData.remoteUser, appData.telnetProgram,
1687                         appData.icsHost, appData.icsPort);
1688             }
1689             return StartChildProcess(buf, "", &icsPR);
1690
1691         }
1692     } else if (appData.useTelnet) {
1693         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1694
1695     } else {
1696         /* TCP socket interface differs somewhat between
1697            Unix and NT; handle details in the front end.
1698            */
1699         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1700     }
1701 }
1702
1703 void EscapeExpand(char *p, char *q)
1704 {       // [HGM] initstring: routine to shape up string arguments
1705         while(*p++ = *q++) if(p[-1] == '\\')
1706             switch(*q++) {
1707                 case 'n': p[-1] = '\n'; break;
1708                 case 'r': p[-1] = '\r'; break;
1709                 case 't': p[-1] = '\t'; break;
1710                 case '\\': p[-1] = '\\'; break;
1711                 case 0: *p = 0; return;
1712                 default: p[-1] = q[-1]; break;
1713             }
1714 }
1715
1716 void
1717 show_bytes(fp, buf, count)
1718      FILE *fp;
1719      char *buf;
1720      int count;
1721 {
1722     while (count--) {
1723         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1724             fprintf(fp, "\\%03o", *buf & 0xff);
1725         } else {
1726             putc(*buf, fp);
1727         }
1728         buf++;
1729     }
1730     fflush(fp);
1731 }
1732
1733 /* Returns an errno value */
1734 int
1735 OutputMaybeTelnet(pr, message, count, outError)
1736      ProcRef pr;
1737      char *message;
1738      int count;
1739      int *outError;
1740 {
1741     char buf[8192], *p, *q, *buflim;
1742     int left, newcount, outcount;
1743
1744     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1745         *appData.gateway != NULLCHAR) {
1746         if (appData.debugMode) {
1747             fprintf(debugFP, ">ICS: ");
1748             show_bytes(debugFP, message, count);
1749             fprintf(debugFP, "\n");
1750         }
1751         return OutputToProcess(pr, message, count, outError);
1752     }
1753
1754     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1755     p = message;
1756     q = buf;
1757     left = count;
1758     newcount = 0;
1759     while (left) {
1760         if (q >= buflim) {
1761             if (appData.debugMode) {
1762                 fprintf(debugFP, ">ICS: ");
1763                 show_bytes(debugFP, buf, newcount);
1764                 fprintf(debugFP, "\n");
1765             }
1766             outcount = OutputToProcess(pr, buf, newcount, outError);
1767             if (outcount < newcount) return -1; /* to be sure */
1768             q = buf;
1769             newcount = 0;
1770         }
1771         if (*p == '\n') {
1772             *q++ = '\r';
1773             newcount++;
1774         } else if (((unsigned char) *p) == TN_IAC) {
1775             *q++ = (char) TN_IAC;
1776             newcount ++;
1777         }
1778         *q++ = *p++;
1779         newcount++;
1780         left--;
1781     }
1782     if (appData.debugMode) {
1783         fprintf(debugFP, ">ICS: ");
1784         show_bytes(debugFP, buf, newcount);
1785         fprintf(debugFP, "\n");
1786     }
1787     outcount = OutputToProcess(pr, buf, newcount, outError);
1788     if (outcount < newcount) return -1; /* to be sure */
1789     return count;
1790 }
1791
1792 void
1793 read_from_player(isr, closure, message, count, error)
1794      InputSourceRef isr;
1795      VOIDSTAR closure;
1796      char *message;
1797      int count;
1798      int error;
1799 {
1800     int outError, outCount;
1801     static int gotEof = 0;
1802
1803     /* Pass data read from player on to ICS */
1804     if (count > 0) {
1805         gotEof = 0;
1806         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1807         if (outCount < count) {
1808             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1809         }
1810     } else if (count < 0) {
1811         RemoveInputSource(isr);
1812         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1813     } else if (gotEof++ > 0) {
1814         RemoveInputSource(isr);
1815         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1816     }
1817 }
1818
1819 void
1820 KeepAlive()
1821 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1822     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1823     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1824     SendToICS("date\n");
1825     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1826 }
1827
1828 /* added routine for printf style output to ics */
1829 void ics_printf(char *format, ...)
1830 {
1831     char buffer[MSG_SIZ];
1832     va_list args;
1833
1834     va_start(args, format);
1835     vsnprintf(buffer, sizeof(buffer), format, args);
1836     buffer[sizeof(buffer)-1] = '\0';
1837     SendToICS(buffer);
1838     va_end(args);
1839 }
1840
1841 void
1842 SendToICS(s)
1843      char *s;
1844 {
1845     int count, outCount, outError;
1846
1847     if (icsPR == NULL) return;
1848
1849     count = strlen(s);
1850     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1851     if (outCount < count) {
1852         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853     }
1854 }
1855
1856 /* This is used for sending logon scripts to the ICS. Sending
1857    without a delay causes problems when using timestamp on ICC
1858    (at least on my machine). */
1859 void
1860 SendToICSDelayed(s,msdelay)
1861      char *s;
1862      long msdelay;
1863 {
1864     int count, outCount, outError;
1865
1866     if (icsPR == NULL) return;
1867
1868     count = strlen(s);
1869     if (appData.debugMode) {
1870         fprintf(debugFP, ">ICS: ");
1871         show_bytes(debugFP, s, count);
1872         fprintf(debugFP, "\n");
1873     }
1874     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1875                                       msdelay);
1876     if (outCount < count) {
1877         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1878     }
1879 }
1880
1881
1882 /* Remove all highlighting escape sequences in s
1883    Also deletes any suffix starting with '('
1884    */
1885 char *
1886 StripHighlightAndTitle(s)
1887      char *s;
1888 {
1889     static char retbuf[MSG_SIZ];
1890     char *p = retbuf;
1891
1892     while (*s != NULLCHAR) {
1893         while (*s == '\033') {
1894             while (*s != NULLCHAR && !isalpha(*s)) s++;
1895             if (*s != NULLCHAR) s++;
1896         }
1897         while (*s != NULLCHAR && *s != '\033') {
1898             if (*s == '(' || *s == '[') {
1899                 *p = NULLCHAR;
1900                 return retbuf;
1901             }
1902             *p++ = *s++;
1903         }
1904     }
1905     *p = NULLCHAR;
1906     return retbuf;
1907 }
1908
1909 /* Remove all highlighting escape sequences in s */
1910 char *
1911 StripHighlight(s)
1912      char *s;
1913 {
1914     static char retbuf[MSG_SIZ];
1915     char *p = retbuf;
1916
1917     while (*s != NULLCHAR) {
1918         while (*s == '\033') {
1919             while (*s != NULLCHAR && !isalpha(*s)) s++;
1920             if (*s != NULLCHAR) s++;
1921         }
1922         while (*s != NULLCHAR && *s != '\033') {
1923             *p++ = *s++;
1924         }
1925     }
1926     *p = NULLCHAR;
1927     return retbuf;
1928 }
1929
1930 char *variantNames[] = VARIANT_NAMES;
1931 char *
1932 VariantName(v)
1933      VariantClass v;
1934 {
1935     return variantNames[v];
1936 }
1937
1938
1939 /* Identify a variant from the strings the chess servers use or the
1940    PGN Variant tag names we use. */
1941 VariantClass
1942 StringToVariant(e)
1943      char *e;
1944 {
1945     char *p;
1946     int wnum = -1;
1947     VariantClass v = VariantNormal;
1948     int i, found = FALSE;
1949     char buf[MSG_SIZ];
1950     int len;
1951
1952     if (!e) return v;
1953
1954     /* [HGM] skip over optional board-size prefixes */
1955     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1956         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1957         while( *e++ != '_');
1958     }
1959
1960     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1961         v = VariantNormal;
1962         found = TRUE;
1963     } else
1964     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1965       if (StrCaseStr(e, variantNames[i])) {
1966         v = (VariantClass) i;
1967         found = TRUE;
1968         break;
1969       }
1970     }
1971
1972     if (!found) {
1973       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1974           || StrCaseStr(e, "wild/fr")
1975           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1976         v = VariantFischeRandom;
1977       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1978                  (i = 1, p = StrCaseStr(e, "w"))) {
1979         p += i;
1980         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1981         if (isdigit(*p)) {
1982           wnum = atoi(p);
1983         } else {
1984           wnum = -1;
1985         }
1986         switch (wnum) {
1987         case 0: /* FICS only, actually */
1988         case 1:
1989           /* Castling legal even if K starts on d-file */
1990           v = VariantWildCastle;
1991           break;
1992         case 2:
1993         case 3:
1994         case 4:
1995           /* Castling illegal even if K & R happen to start in
1996              normal positions. */
1997           v = VariantNoCastle;
1998           break;
1999         case 5:
2000         case 7:
2001         case 8:
2002         case 10:
2003         case 11:
2004         case 12:
2005         case 13:
2006         case 14:
2007         case 15:
2008         case 18:
2009         case 19:
2010           /* Castling legal iff K & R start in normal positions */
2011           v = VariantNormal;
2012           break;
2013         case 6:
2014         case 20:
2015         case 21:
2016           /* Special wilds for position setup; unclear what to do here */
2017           v = VariantLoadable;
2018           break;
2019         case 9:
2020           /* Bizarre ICC game */
2021           v = VariantTwoKings;
2022           break;
2023         case 16:
2024           v = VariantKriegspiel;
2025           break;
2026         case 17:
2027           v = VariantLosers;
2028           break;
2029         case 22:
2030           v = VariantFischeRandom;
2031           break;
2032         case 23:
2033           v = VariantCrazyhouse;
2034           break;
2035         case 24:
2036           v = VariantBughouse;
2037           break;
2038         case 25:
2039           v = Variant3Check;
2040           break;
2041         case 26:
2042           /* Not quite the same as FICS suicide! */
2043           v = VariantGiveaway;
2044           break;
2045         case 27:
2046           v = VariantAtomic;
2047           break;
2048         case 28:
2049           v = VariantShatranj;
2050           break;
2051
2052         /* Temporary names for future ICC types.  The name *will* change in
2053            the next xboard/WinBoard release after ICC defines it. */
2054         case 29:
2055           v = Variant29;
2056           break;
2057         case 30:
2058           v = Variant30;
2059           break;
2060         case 31:
2061           v = Variant31;
2062           break;
2063         case 32:
2064           v = Variant32;
2065           break;
2066         case 33:
2067           v = Variant33;
2068           break;
2069         case 34:
2070           v = Variant34;
2071           break;
2072         case 35:
2073           v = Variant35;
2074           break;
2075         case 36:
2076           v = Variant36;
2077           break;
2078         case 37:
2079           v = VariantShogi;
2080           break;
2081         case 38:
2082           v = VariantXiangqi;
2083           break;
2084         case 39:
2085           v = VariantCourier;
2086           break;
2087         case 40:
2088           v = VariantGothic;
2089           break;
2090         case 41:
2091           v = VariantCapablanca;
2092           break;
2093         case 42:
2094           v = VariantKnightmate;
2095           break;
2096         case 43:
2097           v = VariantFairy;
2098           break;
2099         case 44:
2100           v = VariantCylinder;
2101           break;
2102         case 45:
2103           v = VariantFalcon;
2104           break;
2105         case 46:
2106           v = VariantCapaRandom;
2107           break;
2108         case 47:
2109           v = VariantBerolina;
2110           break;
2111         case 48:
2112           v = VariantJanus;
2113           break;
2114         case 49:
2115           v = VariantSuper;
2116           break;
2117         case 50:
2118           v = VariantGreat;
2119           break;
2120         case -1:
2121           /* Found "wild" or "w" in the string but no number;
2122              must assume it's normal chess. */
2123           v = VariantNormal;
2124           break;
2125         default:
2126           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2127           if( (len > MSG_SIZ) && appData.debugMode )
2128             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2129
2130           DisplayError(buf, 0);
2131           v = VariantUnknown;
2132           break;
2133         }
2134       }
2135     }
2136     if (appData.debugMode) {
2137       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2138               e, wnum, VariantName(v));
2139     }
2140     return v;
2141 }
2142
2143 static int leftover_start = 0, leftover_len = 0;
2144 char star_match[STAR_MATCH_N][MSG_SIZ];
2145
2146 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2147    advance *index beyond it, and set leftover_start to the new value of
2148    *index; else return FALSE.  If pattern contains the character '*', it
2149    matches any sequence of characters not containing '\r', '\n', or the
2150    character following the '*' (if any), and the matched sequence(s) are
2151    copied into star_match.
2152    */
2153 int
2154 looking_at(buf, index, pattern)
2155      char *buf;
2156      int *index;
2157      char *pattern;
2158 {
2159     char *bufp = &buf[*index], *patternp = pattern;
2160     int star_count = 0;
2161     char *matchp = star_match[0];
2162
2163     for (;;) {
2164         if (*patternp == NULLCHAR) {
2165             *index = leftover_start = bufp - buf;
2166             *matchp = NULLCHAR;
2167             return TRUE;
2168         }
2169         if (*bufp == NULLCHAR) return FALSE;
2170         if (*patternp == '*') {
2171             if (*bufp == *(patternp + 1)) {
2172                 *matchp = NULLCHAR;
2173                 matchp = star_match[++star_count];
2174                 patternp += 2;
2175                 bufp++;
2176                 continue;
2177             } else if (*bufp == '\n' || *bufp == '\r') {
2178                 patternp++;
2179                 if (*patternp == NULLCHAR)
2180                   continue;
2181                 else
2182                   return FALSE;
2183             } else {
2184                 *matchp++ = *bufp++;
2185                 continue;
2186             }
2187         }
2188         if (*patternp != *bufp) return FALSE;
2189         patternp++;
2190         bufp++;
2191     }
2192 }
2193
2194 void
2195 SendToPlayer(data, length)
2196      char *data;
2197      int length;
2198 {
2199     int error, outCount;
2200     outCount = OutputToProcess(NoProc, data, length, &error);
2201     if (outCount < length) {
2202         DisplayFatalError(_("Error writing to display"), error, 1);
2203     }
2204 }
2205
2206 void
2207 PackHolding(packed, holding)
2208      char packed[];
2209      char *holding;
2210 {
2211     char *p = holding;
2212     char *q = packed;
2213     int runlength = 0;
2214     int curr = 9999;
2215     do {
2216         if (*p == curr) {
2217             runlength++;
2218         } else {
2219             switch (runlength) {
2220               case 0:
2221                 break;
2222               case 1:
2223                 *q++ = curr;
2224                 break;
2225               case 2:
2226                 *q++ = curr;
2227                 *q++ = curr;
2228                 break;
2229               default:
2230                 sprintf(q, "%d", runlength);
2231                 while (*q) q++;
2232                 *q++ = curr;
2233                 break;
2234             }
2235             runlength = 1;
2236             curr = *p;
2237         }
2238     } while (*p++);
2239     *q = NULLCHAR;
2240 }
2241
2242 /* Telnet protocol requests from the front end */
2243 void
2244 TelnetRequest(ddww, option)
2245      unsigned char ddww, option;
2246 {
2247     unsigned char msg[3];
2248     int outCount, outError;
2249
2250     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2251
2252     if (appData.debugMode) {
2253         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2254         switch (ddww) {
2255           case TN_DO:
2256             ddwwStr = "DO";
2257             break;
2258           case TN_DONT:
2259             ddwwStr = "DONT";
2260             break;
2261           case TN_WILL:
2262             ddwwStr = "WILL";
2263             break;
2264           case TN_WONT:
2265             ddwwStr = "WONT";
2266             break;
2267           default:
2268             ddwwStr = buf1;
2269             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2270             break;
2271         }
2272         switch (option) {
2273           case TN_ECHO:
2274             optionStr = "ECHO";
2275             break;
2276           default:
2277             optionStr = buf2;
2278             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2279             break;
2280         }
2281         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2282     }
2283     msg[0] = TN_IAC;
2284     msg[1] = ddww;
2285     msg[2] = option;
2286     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2287     if (outCount < 3) {
2288         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2289     }
2290 }
2291
2292 void
2293 DoEcho()
2294 {
2295     if (!appData.icsActive) return;
2296     TelnetRequest(TN_DO, TN_ECHO);
2297 }
2298
2299 void
2300 DontEcho()
2301 {
2302     if (!appData.icsActive) return;
2303     TelnetRequest(TN_DONT, TN_ECHO);
2304 }
2305
2306 void
2307 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2308 {
2309     /* put the holdings sent to us by the server on the board holdings area */
2310     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2311     char p;
2312     ChessSquare piece;
2313
2314     if(gameInfo.holdingsWidth < 2)  return;
2315     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2316         return; // prevent overwriting by pre-board holdings
2317
2318     if( (int)lowestPiece >= BlackPawn ) {
2319         holdingsColumn = 0;
2320         countsColumn = 1;
2321         holdingsStartRow = BOARD_HEIGHT-1;
2322         direction = -1;
2323     } else {
2324         holdingsColumn = BOARD_WIDTH-1;
2325         countsColumn = BOARD_WIDTH-2;
2326         holdingsStartRow = 0;
2327         direction = 1;
2328     }
2329
2330     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2331         board[i][holdingsColumn] = EmptySquare;
2332         board[i][countsColumn]   = (ChessSquare) 0;
2333     }
2334     while( (p=*holdings++) != NULLCHAR ) {
2335         piece = CharToPiece( ToUpper(p) );
2336         if(piece == EmptySquare) continue;
2337         /*j = (int) piece - (int) WhitePawn;*/
2338         j = PieceToNumber(piece);
2339         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2340         if(j < 0) continue;               /* should not happen */
2341         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2342         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2343         board[holdingsStartRow+j*direction][countsColumn]++;
2344     }
2345 }
2346
2347
2348 void
2349 VariantSwitch(Board board, VariantClass newVariant)
2350 {
2351    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2352    static Board oldBoard;
2353
2354    startedFromPositionFile = FALSE;
2355    if(gameInfo.variant == newVariant) return;
2356
2357    /* [HGM] This routine is called each time an assignment is made to
2358     * gameInfo.variant during a game, to make sure the board sizes
2359     * are set to match the new variant. If that means adding or deleting
2360     * holdings, we shift the playing board accordingly
2361     * This kludge is needed because in ICS observe mode, we get boards
2362     * of an ongoing game without knowing the variant, and learn about the
2363     * latter only later. This can be because of the move list we requested,
2364     * in which case the game history is refilled from the beginning anyway,
2365     * but also when receiving holdings of a crazyhouse game. In the latter
2366     * case we want to add those holdings to the already received position.
2367     */
2368
2369
2370    if (appData.debugMode) {
2371      fprintf(debugFP, "Switch board from %s to %s\n",
2372              VariantName(gameInfo.variant), VariantName(newVariant));
2373      setbuf(debugFP, NULL);
2374    }
2375    shuffleOpenings = 0;       /* [HGM] shuffle */
2376    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2377    switch(newVariant)
2378      {
2379      case VariantShogi:
2380        newWidth = 9;  newHeight = 9;
2381        gameInfo.holdingsSize = 7;
2382      case VariantBughouse:
2383      case VariantCrazyhouse:
2384        newHoldingsWidth = 2; break;
2385      case VariantGreat:
2386        newWidth = 10;
2387      case VariantSuper:
2388        newHoldingsWidth = 2;
2389        gameInfo.holdingsSize = 8;
2390        break;
2391      case VariantGothic:
2392      case VariantCapablanca:
2393      case VariantCapaRandom:
2394        newWidth = 10;
2395      default:
2396        newHoldingsWidth = gameInfo.holdingsSize = 0;
2397      };
2398
2399    if(newWidth  != gameInfo.boardWidth  ||
2400       newHeight != gameInfo.boardHeight ||
2401       newHoldingsWidth != gameInfo.holdingsWidth ) {
2402
2403      /* shift position to new playing area, if needed */
2404      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2405        for(i=0; i<BOARD_HEIGHT; i++)
2406          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2407            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2408              board[i][j];
2409        for(i=0; i<newHeight; i++) {
2410          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2411          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2412        }
2413      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2414        for(i=0; i<BOARD_HEIGHT; i++)
2415          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2416            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2417              board[i][j];
2418      }
2419      gameInfo.boardWidth  = newWidth;
2420      gameInfo.boardHeight = newHeight;
2421      gameInfo.holdingsWidth = newHoldingsWidth;
2422      gameInfo.variant = newVariant;
2423      InitDrawingSizes(-2, 0);
2424    } else gameInfo.variant = newVariant;
2425    CopyBoard(oldBoard, board);   // remember correctly formatted board
2426      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2427    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2428 }
2429
2430 static int loggedOn = FALSE;
2431
2432 /*-- Game start info cache: --*/
2433 int gs_gamenum;
2434 char gs_kind[MSG_SIZ];
2435 static char player1Name[128] = "";
2436 static char player2Name[128] = "";
2437 static char cont_seq[] = "\n\\   ";
2438 static int player1Rating = -1;
2439 static int player2Rating = -1;
2440 /*----------------------------*/
2441
2442 ColorClass curColor = ColorNormal;
2443 int suppressKibitz = 0;
2444
2445 // [HGM] seekgraph
2446 Boolean soughtPending = FALSE;
2447 Boolean seekGraphUp;
2448 #define MAX_SEEK_ADS 200
2449 #define SQUARE 0x80
2450 char *seekAdList[MAX_SEEK_ADS];
2451 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2452 float tcList[MAX_SEEK_ADS];
2453 char colorList[MAX_SEEK_ADS];
2454 int nrOfSeekAds = 0;
2455 int minRating = 1010, maxRating = 2800;
2456 int hMargin = 10, vMargin = 20, h, w;
2457 extern int squareSize, lineGap;
2458
2459 void
2460 PlotSeekAd(int i)
2461 {
2462         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2463         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2464         if(r < minRating+100 && r >=0 ) r = minRating+100;
2465         if(r > maxRating) r = maxRating;
2466         if(tc < 1.) tc = 1.;
2467         if(tc > 95.) tc = 95.;
2468         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2469         y = ((double)r - minRating)/(maxRating - minRating)
2470             * (h-vMargin-squareSize/8-1) + vMargin;
2471         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2472         if(strstr(seekAdList[i], " u ")) color = 1;
2473         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2474            !strstr(seekAdList[i], "bullet") &&
2475            !strstr(seekAdList[i], "blitz") &&
2476            !strstr(seekAdList[i], "standard") ) color = 2;
2477         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2478         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2479 }
2480
2481 void
2482 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2483 {
2484         char buf[MSG_SIZ], *ext = "";
2485         VariantClass v = StringToVariant(type);
2486         if(strstr(type, "wild")) {
2487             ext = type + 4; // append wild number
2488             if(v == VariantFischeRandom) type = "chess960"; else
2489             if(v == VariantLoadable) type = "setup"; else
2490             type = VariantName(v);
2491         }
2492         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2493         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2494             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2495             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2496             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2497             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2498             seekNrList[nrOfSeekAds] = nr;
2499             zList[nrOfSeekAds] = 0;
2500             seekAdList[nrOfSeekAds++] = StrSave(buf);
2501             if(plot) PlotSeekAd(nrOfSeekAds-1);
2502         }
2503 }
2504
2505 void
2506 EraseSeekDot(int i)
2507 {
2508     int x = xList[i], y = yList[i], d=squareSize/4, k;
2509     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2510     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2511     // now replot every dot that overlapped
2512     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2513         int xx = xList[k], yy = yList[k];
2514         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2515             DrawSeekDot(xx, yy, colorList[k]);
2516     }
2517 }
2518
2519 void
2520 RemoveSeekAd(int nr)
2521 {
2522         int i;
2523         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2524             EraseSeekDot(i);
2525             if(seekAdList[i]) free(seekAdList[i]);
2526             seekAdList[i] = seekAdList[--nrOfSeekAds];
2527             seekNrList[i] = seekNrList[nrOfSeekAds];
2528             ratingList[i] = ratingList[nrOfSeekAds];
2529             colorList[i]  = colorList[nrOfSeekAds];
2530             tcList[i] = tcList[nrOfSeekAds];
2531             xList[i]  = xList[nrOfSeekAds];
2532             yList[i]  = yList[nrOfSeekAds];
2533             zList[i]  = zList[nrOfSeekAds];
2534             seekAdList[nrOfSeekAds] = NULL;
2535             break;
2536         }
2537 }
2538
2539 Boolean
2540 MatchSoughtLine(char *line)
2541 {
2542     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2543     int nr, base, inc, u=0; char dummy;
2544
2545     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2546        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2547        (u=1) &&
2548        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2549         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2550         // match: compact and save the line
2551         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2552         return TRUE;
2553     }
2554     return FALSE;
2555 }
2556
2557 int
2558 DrawSeekGraph()
2559 {
2560     int i;
2561     if(!seekGraphUp) return FALSE;
2562     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2563     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2564
2565     DrawSeekBackground(0, 0, w, h);
2566     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2567     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2568     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2569         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2570         yy = h-1-yy;
2571         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2572         if(i%500 == 0) {
2573             char buf[MSG_SIZ];
2574             snprintf(buf, MSG_SIZ, "%d", i);
2575             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2576         }
2577     }
2578     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2579     for(i=1; i<100; i+=(i<10?1:5)) {
2580         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2581         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2582         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2583             char buf[MSG_SIZ];
2584             snprintf(buf, MSG_SIZ, "%d", i);
2585             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2586         }
2587     }
2588     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2589     return TRUE;
2590 }
2591
2592 int SeekGraphClick(ClickType click, int x, int y, int moving)
2593 {
2594     static int lastDown = 0, displayed = 0, lastSecond;
2595     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2596         if(click == Release || moving) return FALSE;
2597         nrOfSeekAds = 0;
2598         soughtPending = TRUE;
2599         SendToICS(ics_prefix);
2600         SendToICS("sought\n"); // should this be "sought all"?
2601     } else { // issue challenge based on clicked ad
2602         int dist = 10000; int i, closest = 0, second = 0;
2603         for(i=0; i<nrOfSeekAds; i++) {
2604             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2605             if(d < dist) { dist = d; closest = i; }
2606             second += (d - zList[i] < 120); // count in-range ads
2607             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2608         }
2609         if(dist < 120) {
2610             char buf[MSG_SIZ];
2611             second = (second > 1);
2612             if(displayed != closest || second != lastSecond) {
2613                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2614                 lastSecond = second; displayed = closest;
2615             }
2616             if(click == Press) {
2617                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2618                 lastDown = closest;
2619                 return TRUE;
2620             } // on press 'hit', only show info
2621             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2622             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2623             SendToICS(ics_prefix);
2624             SendToICS(buf);
2625             return TRUE; // let incoming board of started game pop down the graph
2626         } else if(click == Release) { // release 'miss' is ignored
2627             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2628             if(moving == 2) { // right up-click
2629                 nrOfSeekAds = 0; // refresh graph
2630                 soughtPending = TRUE;
2631                 SendToICS(ics_prefix);
2632                 SendToICS("sought\n"); // should this be "sought all"?
2633             }
2634             return TRUE;
2635         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2636         // press miss or release hit 'pop down' seek graph
2637         seekGraphUp = FALSE;
2638         DrawPosition(TRUE, NULL);
2639     }
2640     return TRUE;
2641 }
2642
2643 void
2644 read_from_ics(isr, closure, data, count, error)
2645      InputSourceRef isr;
2646      VOIDSTAR closure;
2647      char *data;
2648      int count;
2649      int error;
2650 {
2651 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2652 #define STARTED_NONE 0
2653 #define STARTED_MOVES 1
2654 #define STARTED_BOARD 2
2655 #define STARTED_OBSERVE 3
2656 #define STARTED_HOLDINGS 4
2657 #define STARTED_CHATTER 5
2658 #define STARTED_COMMENT 6
2659 #define STARTED_MOVES_NOHIDE 7
2660
2661     static int started = STARTED_NONE;
2662     static char parse[20000];
2663     static int parse_pos = 0;
2664     static char buf[BUF_SIZE + 1];
2665     static int firstTime = TRUE, intfSet = FALSE;
2666     static ColorClass prevColor = ColorNormal;
2667     static int savingComment = FALSE;
2668     static int cmatch = 0; // continuation sequence match
2669     char *bp;
2670     char str[MSG_SIZ];
2671     int i, oldi;
2672     int buf_len;
2673     int next_out;
2674     int tkind;
2675     int backup;    /* [DM] For zippy color lines */
2676     char *p;
2677     char talker[MSG_SIZ]; // [HGM] chat
2678     int channel;
2679
2680     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2681
2682     if (appData.debugMode) {
2683       if (!error) {
2684         fprintf(debugFP, "<ICS: ");
2685         show_bytes(debugFP, data, count);
2686         fprintf(debugFP, "\n");
2687       }
2688     }
2689
2690     if (appData.debugMode) { int f = forwardMostMove;
2691         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2692                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2693                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2694     }
2695     if (count > 0) {
2696         /* If last read ended with a partial line that we couldn't parse,
2697            prepend it to the new read and try again. */
2698         if (leftover_len > 0) {
2699             for (i=0; i<leftover_len; i++)
2700               buf[i] = buf[leftover_start + i];
2701         }
2702
2703     /* copy new characters into the buffer */
2704     bp = buf + leftover_len;
2705     buf_len=leftover_len;
2706     for (i=0; i<count; i++)
2707     {
2708         // ignore these
2709         if (data[i] == '\r')
2710             continue;
2711
2712         // join lines split by ICS?
2713         if (!appData.noJoin)
2714         {
2715             /*
2716                 Joining just consists of finding matches against the
2717                 continuation sequence, and discarding that sequence
2718                 if found instead of copying it.  So, until a match
2719                 fails, there's nothing to do since it might be the
2720                 complete sequence, and thus, something we don't want
2721                 copied.
2722             */
2723             if (data[i] == cont_seq[cmatch])
2724             {
2725                 cmatch++;
2726                 if (cmatch == strlen(cont_seq))
2727                 {
2728                     cmatch = 0; // complete match.  just reset the counter
2729
2730                     /*
2731                         it's possible for the ICS to not include the space
2732                         at the end of the last word, making our [correct]
2733                         join operation fuse two separate words.  the server
2734                         does this when the space occurs at the width setting.
2735                     */
2736                     if (!buf_len || buf[buf_len-1] != ' ')
2737                     {
2738                         *bp++ = ' ';
2739                         buf_len++;
2740                     }
2741                 }
2742                 continue;
2743             }
2744             else if (cmatch)
2745             {
2746                 /*
2747                     match failed, so we have to copy what matched before
2748                     falling through and copying this character.  In reality,
2749                     this will only ever be just the newline character, but
2750                     it doesn't hurt to be precise.
2751                 */
2752                 strncpy(bp, cont_seq, cmatch);
2753                 bp += cmatch;
2754                 buf_len += cmatch;
2755                 cmatch = 0;
2756             }
2757         }
2758
2759         // copy this char
2760         *bp++ = data[i];
2761         buf_len++;
2762     }
2763
2764         buf[buf_len] = NULLCHAR;
2765 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2766         next_out = 0;
2767         leftover_start = 0;
2768
2769         i = 0;
2770         while (i < buf_len) {
2771             /* Deal with part of the TELNET option negotiation
2772                protocol.  We refuse to do anything beyond the
2773                defaults, except that we allow the WILL ECHO option,
2774                which ICS uses to turn off password echoing when we are
2775                directly connected to it.  We reject this option
2776                if localLineEditing mode is on (always on in xboard)
2777                and we are talking to port 23, which might be a real
2778                telnet server that will try to keep WILL ECHO on permanently.
2779              */
2780             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2781                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2782                 unsigned char option;
2783                 oldi = i;
2784                 switch ((unsigned char) buf[++i]) {
2785                   case TN_WILL:
2786                     if (appData.debugMode)
2787                       fprintf(debugFP, "\n<WILL ");
2788                     switch (option = (unsigned char) buf[++i]) {
2789                       case TN_ECHO:
2790                         if (appData.debugMode)
2791                           fprintf(debugFP, "ECHO ");
2792                         /* Reply only if this is a change, according
2793                            to the protocol rules. */
2794                         if (remoteEchoOption) break;
2795                         if (appData.localLineEditing &&
2796                             atoi(appData.icsPort) == TN_PORT) {
2797                             TelnetRequest(TN_DONT, TN_ECHO);
2798                         } else {
2799                             EchoOff();
2800                             TelnetRequest(TN_DO, TN_ECHO);
2801                             remoteEchoOption = TRUE;
2802                         }
2803                         break;
2804                       default:
2805                         if (appData.debugMode)
2806                           fprintf(debugFP, "%d ", option);
2807                         /* Whatever this is, we don't want it. */
2808                         TelnetRequest(TN_DONT, option);
2809                         break;
2810                     }
2811                     break;
2812                   case TN_WONT:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<WONT ");
2815                     switch (option = (unsigned char) buf[++i]) {
2816                       case TN_ECHO:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "ECHO ");
2819                         /* Reply only if this is a change, according
2820                            to the protocol rules. */
2821                         if (!remoteEchoOption) break;
2822                         EchoOn();
2823                         TelnetRequest(TN_DONT, TN_ECHO);
2824                         remoteEchoOption = FALSE;
2825                         break;
2826                       default:
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", (unsigned char) option);
2829                         /* Whatever this is, it must already be turned
2830                            off, because we never agree to turn on
2831                            anything non-default, so according to the
2832                            protocol rules, we don't reply. */
2833                         break;
2834                     }
2835                     break;
2836                   case TN_DO:
2837                     if (appData.debugMode)
2838                       fprintf(debugFP, "\n<DO ");
2839                     switch (option = (unsigned char) buf[++i]) {
2840                       default:
2841                         /* Whatever this is, we refuse to do it. */
2842                         if (appData.debugMode)
2843                           fprintf(debugFP, "%d ", option);
2844                         TelnetRequest(TN_WONT, option);
2845                         break;
2846                     }
2847                     break;
2848                   case TN_DONT:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<DONT ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       default:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "%d ", option);
2855                         /* Whatever this is, we are already not doing
2856                            it, because we never agree to do anything
2857                            non-default, so according to the protocol
2858                            rules, we don't reply. */
2859                         break;
2860                     }
2861                     break;
2862                   case TN_IAC:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<IAC ");
2865                     /* Doubled IAC; pass it through */
2866                     i--;
2867                     break;
2868                   default:
2869                     if (appData.debugMode)
2870                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2871                     /* Drop all other telnet commands on the floor */
2872                     break;
2873                 }
2874                 if (oldi > next_out)
2875                   SendToPlayer(&buf[next_out], oldi - next_out);
2876                 if (++i > next_out)
2877                   next_out = i;
2878                 continue;
2879             }
2880
2881             /* OK, this at least will *usually* work */
2882             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2883                 loggedOn = TRUE;
2884             }
2885
2886             if (loggedOn && !intfSet) {
2887                 if (ics_type == ICS_ICC) {
2888                   snprintf(str, MSG_SIZ,
2889                           "/set-quietly interface %s\n/set-quietly style 12\n",
2890                           programVersion);
2891                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2892                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2893                 } else if (ics_type == ICS_CHESSNET) {
2894                   snprintf(str, MSG_SIZ, "/style 12\n");
2895                 } else {
2896                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2897                   strcat(str, programVersion);
2898                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2899                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2900                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2901 #ifdef WIN32
2902                   strcat(str, "$iset nohighlight 1\n");
2903 #endif
2904                   strcat(str, "$iset lock 1\n$style 12\n");
2905                 }
2906                 SendToICS(str);
2907                 NotifyFrontendLogin();
2908                 intfSet = TRUE;
2909             }
2910
2911             if (started == STARTED_COMMENT) {
2912                 /* Accumulate characters in comment */
2913                 parse[parse_pos++] = buf[i];
2914                 if (buf[i] == '\n') {
2915                     parse[parse_pos] = NULLCHAR;
2916                     if(chattingPartner>=0) {
2917                         char mess[MSG_SIZ];
2918                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2919                         OutputChatMessage(chattingPartner, mess);
2920                         chattingPartner = -1;
2921                         next_out = i+1; // [HGM] suppress printing in ICS window
2922                     } else
2923                     if(!suppressKibitz) // [HGM] kibitz
2924                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2925                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2926                         int nrDigit = 0, nrAlph = 0, j;
2927                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2928                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2929                         parse[parse_pos] = NULLCHAR;
2930                         // try to be smart: if it does not look like search info, it should go to
2931                         // ICS interaction window after all, not to engine-output window.
2932                         for(j=0; j<parse_pos; j++) { // count letters and digits
2933                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2934                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2935                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2936                         }
2937                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2938                             int depth=0; float score;
2939                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2940                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2941                                 pvInfoList[forwardMostMove-1].depth = depth;
2942                                 pvInfoList[forwardMostMove-1].score = 100*score;
2943                             }
2944                             OutputKibitz(suppressKibitz, parse);
2945                         } else {
2946                             char tmp[MSG_SIZ];
2947                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2948                             SendToPlayer(tmp, strlen(tmp));
2949                         }
2950                         next_out = i+1; // [HGM] suppress printing in ICS window
2951                     }
2952                     started = STARTED_NONE;
2953                 } else {
2954                     /* Don't match patterns against characters in comment */
2955                     i++;
2956                     continue;
2957                 }
2958             }
2959             if (started == STARTED_CHATTER) {
2960                 if (buf[i] != '\n') {
2961                     /* Don't match patterns against characters in chatter */
2962                     i++;
2963                     continue;
2964                 }
2965                 started = STARTED_NONE;
2966                 if(suppressKibitz) next_out = i+1;
2967             }
2968
2969             /* Kludge to deal with rcmd protocol */
2970             if (firstTime && looking_at(buf, &i, "\001*")) {
2971                 DisplayFatalError(&buf[1], 0, 1);
2972                 continue;
2973             } else {
2974                 firstTime = FALSE;
2975             }
2976
2977             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2978                 ics_type = ICS_ICC;
2979                 ics_prefix = "/";
2980                 if (appData.debugMode)
2981                   fprintf(debugFP, "ics_type %d\n", ics_type);
2982                 continue;
2983             }
2984             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2985                 ics_type = ICS_FICS;
2986                 ics_prefix = "$";
2987                 if (appData.debugMode)
2988                   fprintf(debugFP, "ics_type %d\n", ics_type);
2989                 continue;
2990             }
2991             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2992                 ics_type = ICS_CHESSNET;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998
2999             if (!loggedOn &&
3000                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3001                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3002                  looking_at(buf, &i, "will be \"*\""))) {
3003               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3004               continue;
3005             }
3006
3007             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3008               char buf[MSG_SIZ];
3009               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3010               DisplayIcsInteractionTitle(buf);
3011               have_set_title = TRUE;
3012             }
3013
3014             /* skip finger notes */
3015             if (started == STARTED_NONE &&
3016                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3017                  (buf[i] == '1' && buf[i+1] == '0')) &&
3018                 buf[i+2] == ':' && buf[i+3] == ' ') {
3019               started = STARTED_CHATTER;
3020               i += 3;
3021               continue;
3022             }
3023
3024             oldi = i;
3025             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3026             if(appData.seekGraph) {
3027                 if(soughtPending && MatchSoughtLine(buf+i)) {
3028                     i = strstr(buf+i, "rated") - buf;
3029                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030                     next_out = leftover_start = i;
3031                     started = STARTED_CHATTER;
3032                     suppressKibitz = TRUE;
3033                     continue;
3034                 }
3035                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3036                         && looking_at(buf, &i, "* ads displayed")) {
3037                     soughtPending = FALSE;
3038                     seekGraphUp = TRUE;
3039                     DrawSeekGraph();
3040                     continue;
3041                 }
3042                 if(appData.autoRefresh) {
3043                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3044                         int s = (ics_type == ICS_ICC); // ICC format differs
3045                         if(seekGraphUp)
3046                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3047                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3048                         looking_at(buf, &i, "*% "); // eat prompt
3049                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3050                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3051                         next_out = i; // suppress
3052                         continue;
3053                     }
3054                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3055                         char *p = star_match[0];
3056                         while(*p) {
3057                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3058                             while(*p && *p++ != ' '); // next
3059                         }
3060                         looking_at(buf, &i, "*% "); // eat prompt
3061                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3062                         next_out = i;
3063                         continue;
3064                     }
3065                 }
3066             }
3067
3068             /* skip formula vars */
3069             if (started == STARTED_NONE &&
3070                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3071               started = STARTED_CHATTER;
3072               i += 3;
3073               continue;
3074             }
3075
3076             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3077             if (appData.autoKibitz && started == STARTED_NONE &&
3078                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3079                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3080                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3081                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3082                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3083                         suppressKibitz = TRUE;
3084                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3085                         next_out = i;
3086                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3087                                 && (gameMode == IcsPlayingWhite)) ||
3088                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3089                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3090                             started = STARTED_CHATTER; // own kibitz we simply discard
3091                         else {
3092                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3093                             parse_pos = 0; parse[0] = NULLCHAR;
3094                             savingComment = TRUE;
3095                             suppressKibitz = gameMode != IcsObserving ? 2 :
3096                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3097                         }
3098                         continue;
3099                 } else
3100                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3101                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3102                          && atoi(star_match[0])) {
3103                     // suppress the acknowledgements of our own autoKibitz
3104                     char *p;
3105                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3107                     SendToPlayer(star_match[0], strlen(star_match[0]));
3108                     if(looking_at(buf, &i, "*% ")) // eat prompt
3109                         suppressKibitz = FALSE;
3110                     next_out = i;
3111                     continue;
3112                 }
3113             } // [HGM] kibitz: end of patch
3114
3115             // [HGM] chat: intercept tells by users for which we have an open chat window
3116             channel = -1;
3117             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3118                                            looking_at(buf, &i, "* whispers:") ||
3119                                            looking_at(buf, &i, "* kibitzes:") ||
3120                                            looking_at(buf, &i, "* shouts:") ||
3121                                            looking_at(buf, &i, "* c-shouts:") ||
3122                                            looking_at(buf, &i, "--> * ") ||
3123                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3124                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3125                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3126                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3127                 int p;
3128                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3129                 chattingPartner = -1;
3130
3131                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3132                 for(p=0; p<MAX_CHAT; p++) {
3133                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3134                     talker[0] = '['; strcat(talker, "] ");
3135                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3136                     chattingPartner = p; break;
3137                     }
3138                 } else
3139                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3140                 for(p=0; p<MAX_CHAT; p++) {
3141                     if(!strcmp("kibitzes", chatPartner[p])) {
3142                         talker[0] = '['; strcat(talker, "] ");
3143                         chattingPartner = p; break;
3144                     }
3145                 } else
3146                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3147                 for(p=0; p<MAX_CHAT; p++) {
3148                     if(!strcmp("whispers", chatPartner[p])) {
3149                         talker[0] = '['; strcat(talker, "] ");
3150                         chattingPartner = p; break;
3151                     }
3152                 } else
3153                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3154                   if(buf[i-8] == '-' && buf[i-3] == 't')
3155                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3156                     if(!strcmp("c-shouts", chatPartner[p])) {
3157                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3158                         chattingPartner = p; break;
3159                     }
3160                   }
3161                   if(chattingPartner < 0)
3162                   for(p=0; p<MAX_CHAT; p++) {
3163                     if(!strcmp("shouts", chatPartner[p])) {
3164                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3165                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3166                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3167                         chattingPartner = p; break;
3168                     }
3169                   }
3170                 }
3171                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3172                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3173                     talker[0] = 0; Colorize(ColorTell, FALSE);
3174                     chattingPartner = p; break;
3175                 }
3176                 if(chattingPartner<0) i = oldi; else {
3177                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3178                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3179                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3180                     started = STARTED_COMMENT;
3181                     parse_pos = 0; parse[0] = NULLCHAR;
3182                     savingComment = 3 + chattingPartner; // counts as TRUE
3183                     suppressKibitz = TRUE;
3184                     continue;
3185                 }
3186             } // [HGM] chat: end of patch
3187
3188           backup = i;
3189             if (appData.zippyTalk || appData.zippyPlay) {
3190                 /* [DM] Backup address for color zippy lines */
3191 #if ZIPPY
3192                if (loggedOn == TRUE)
3193                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3194                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3195 #endif
3196             } // [DM] 'else { ' deleted
3197                 if (
3198                     /* Regular tells and says */
3199                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3200                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3201                     looking_at(buf, &i, "* says: ") ||
3202                     /* Don't color "message" or "messages" output */
3203                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3204                     looking_at(buf, &i, "*. * at *:*: ") ||
3205                     looking_at(buf, &i, "--* (*:*): ") ||
3206                     /* Message notifications (same color as tells) */
3207                     looking_at(buf, &i, "* has left a message ") ||
3208                     looking_at(buf, &i, "* just sent you a message:\n") ||
3209                     /* Whispers and kibitzes */
3210                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3211                     looking_at(buf, &i, "* kibitzes: ") ||
3212                     /* Channel tells */
3213                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3214
3215                   if (tkind == 1 && strchr(star_match[0], ':')) {
3216                       /* Avoid "tells you:" spoofs in channels */
3217                      tkind = 3;
3218                   }
3219                   if (star_match[0][0] == NULLCHAR ||
3220                       strchr(star_match[0], ' ') ||
3221                       (tkind == 3 && strchr(star_match[1], ' '))) {
3222                     /* Reject bogus matches */
3223                     i = oldi;
3224                   } else {
3225                     if (appData.colorize) {
3226                       if (oldi > next_out) {
3227                         SendToPlayer(&buf[next_out], oldi - next_out);
3228                         next_out = oldi;
3229                       }
3230                       switch (tkind) {
3231                       case 1:
3232                         Colorize(ColorTell, FALSE);
3233                         curColor = ColorTell;
3234                         break;
3235                       case 2:
3236                         Colorize(ColorKibitz, FALSE);
3237                         curColor = ColorKibitz;
3238                         break;
3239                       case 3:
3240                         p = strrchr(star_match[1], '(');
3241                         if (p == NULL) {
3242                           p = star_match[1];
3243                         } else {
3244                           p++;
3245                         }
3246                         if (atoi(p) == 1) {
3247                           Colorize(ColorChannel1, FALSE);
3248                           curColor = ColorChannel1;
3249                         } else {
3250                           Colorize(ColorChannel, FALSE);
3251                           curColor = ColorChannel;
3252                         }
3253                         break;
3254                       case 5:
3255                         curColor = ColorNormal;
3256                         break;
3257                       }
3258                     }
3259                     if (started == STARTED_NONE && appData.autoComment &&
3260                         (gameMode == IcsObserving ||
3261                          gameMode == IcsPlayingWhite ||
3262                          gameMode == IcsPlayingBlack)) {
3263                       parse_pos = i - oldi;
3264                       memcpy(parse, &buf[oldi], parse_pos);
3265                       parse[parse_pos] = NULLCHAR;
3266                       started = STARTED_COMMENT;
3267                       savingComment = TRUE;
3268                     } else {
3269                       started = STARTED_CHATTER;
3270                       savingComment = FALSE;
3271                     }
3272                     loggedOn = TRUE;
3273                     continue;
3274                   }
3275                 }
3276
3277                 if (looking_at(buf, &i, "* s-shouts: ") ||
3278                     looking_at(buf, &i, "* c-shouts: ")) {
3279                     if (appData.colorize) {
3280                         if (oldi > next_out) {
3281                             SendToPlayer(&buf[next_out], oldi - next_out);
3282                             next_out = oldi;
3283                         }
3284                         Colorize(ColorSShout, FALSE);
3285                         curColor = ColorSShout;
3286                     }
3287                     loggedOn = TRUE;
3288                     started = STARTED_CHATTER;
3289                     continue;
3290                 }
3291
3292                 if (looking_at(buf, &i, "--->")) {
3293                     loggedOn = TRUE;
3294                     continue;
3295                 }
3296
3297                 if (looking_at(buf, &i, "* shouts: ") ||
3298                     looking_at(buf, &i, "--> ")) {
3299                     if (appData.colorize) {
3300                         if (oldi > next_out) {
3301                             SendToPlayer(&buf[next_out], oldi - next_out);
3302                             next_out = oldi;
3303                         }
3304                         Colorize(ColorShout, FALSE);
3305                         curColor = ColorShout;
3306                     }
3307                     loggedOn = TRUE;
3308                     started = STARTED_CHATTER;
3309                     continue;
3310                 }
3311
3312                 if (looking_at( buf, &i, "Challenge:")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorChallenge, FALSE);
3319                         curColor = ColorChallenge;
3320                     }
3321                     loggedOn = TRUE;
3322                     continue;
3323                 }
3324
3325                 if (looking_at(buf, &i, "* offers you") ||
3326                     looking_at(buf, &i, "* offers to be") ||
3327                     looking_at(buf, &i, "* would like to") ||
3328                     looking_at(buf, &i, "* requests to") ||
3329                     looking_at(buf, &i, "Your opponent offers") ||
3330                     looking_at(buf, &i, "Your opponent requests")) {
3331
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorRequest, FALSE);
3338                         curColor = ColorRequest;
3339                     }
3340                     continue;
3341                 }
3342
3343                 if (looking_at(buf, &i, "* (*) seeking")) {
3344                     if (appData.colorize) {
3345                         if (oldi > next_out) {
3346                             SendToPlayer(&buf[next_out], oldi - next_out);
3347                             next_out = oldi;
3348                         }
3349                         Colorize(ColorSeek, FALSE);
3350                         curColor = ColorSeek;
3351                     }
3352                     continue;
3353             }
3354
3355           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3356
3357             if (looking_at(buf, &i, "\\   ")) {
3358                 if (prevColor != ColorNormal) {
3359                     if (oldi > next_out) {
3360                         SendToPlayer(&buf[next_out], oldi - next_out);
3361                         next_out = oldi;
3362                     }
3363                     Colorize(prevColor, TRUE);
3364                     curColor = prevColor;
3365                 }
3366                 if (savingComment) {
3367                     parse_pos = i - oldi;
3368                     memcpy(parse, &buf[oldi], parse_pos);
3369                     parse[parse_pos] = NULLCHAR;
3370                     started = STARTED_COMMENT;
3371                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3372                         chattingPartner = savingComment - 3; // kludge to remember the box
3373                 } else {
3374                     started = STARTED_CHATTER;
3375                 }
3376                 continue;
3377             }
3378
3379             if (looking_at(buf, &i, "Black Strength :") ||
3380                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3381                 looking_at(buf, &i, "<10>") ||
3382                 looking_at(buf, &i, "#@#")) {
3383                 /* Wrong board style */
3384                 loggedOn = TRUE;
3385                 SendToICS(ics_prefix);
3386                 SendToICS("set style 12\n");
3387                 SendToICS(ics_prefix);
3388                 SendToICS("refresh\n");
3389                 continue;
3390             }
3391
3392             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3393                 ICSInitScript();
3394                 have_sent_ICS_logon = 1;
3395                 continue;
3396             }
3397
3398             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3399                 (looking_at(buf, &i, "\n<12> ") ||
3400                  looking_at(buf, &i, "<12> "))) {
3401                 loggedOn = TRUE;
3402                 if (oldi > next_out) {
3403                     SendToPlayer(&buf[next_out], oldi - next_out);
3404                 }
3405                 next_out = i;
3406                 started = STARTED_BOARD;
3407                 parse_pos = 0;
3408                 continue;
3409             }
3410
3411             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3412                 looking_at(buf, &i, "<b1> ")) {
3413                 if (oldi > next_out) {
3414                     SendToPlayer(&buf[next_out], oldi - next_out);
3415                 }
3416                 next_out = i;
3417                 started = STARTED_HOLDINGS;
3418                 parse_pos = 0;
3419                 continue;
3420             }
3421
3422             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3423                 loggedOn = TRUE;
3424                 /* Header for a move list -- first line */
3425
3426                 switch (ics_getting_history) {
3427                   case H_FALSE:
3428                     switch (gameMode) {
3429                       case IcsIdle:
3430                       case BeginningOfGame:
3431                         /* User typed "moves" or "oldmoves" while we
3432                            were idle.  Pretend we asked for these
3433                            moves and soak them up so user can step
3434                            through them and/or save them.
3435                            */
3436                         Reset(FALSE, TRUE);
3437                         gameMode = IcsObserving;
3438                         ModeHighlight();
3439                         ics_gamenum = -1;
3440                         ics_getting_history = H_GOT_UNREQ_HEADER;
3441                         break;
3442                       case EditGame: /*?*/
3443                       case EditPosition: /*?*/
3444                         /* Should above feature work in these modes too? */
3445                         /* For now it doesn't */
3446                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3447                         break;
3448                       default:
3449                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3450                         break;
3451                     }
3452                     break;
3453                   case H_REQUESTED:
3454                     /* Is this the right one? */
3455                     if (gameInfo.white && gameInfo.black &&
3456                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3457                         strcmp(gameInfo.black, star_match[2]) == 0) {
3458                         /* All is well */
3459                         ics_getting_history = H_GOT_REQ_HEADER;
3460                     }
3461                     break;
3462                   case H_GOT_REQ_HEADER:
3463                   case H_GOT_UNREQ_HEADER:
3464                   case H_GOT_UNWANTED_HEADER:
3465                   case H_GETTING_MOVES:
3466                     /* Should not happen */
3467                     DisplayError(_("Error gathering move list: two headers"), 0);
3468                     ics_getting_history = H_FALSE;
3469                     break;
3470                 }
3471
3472                 /* Save player ratings into gameInfo if needed */
3473                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3474                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3475                     (gameInfo.whiteRating == -1 ||
3476                      gameInfo.blackRating == -1)) {
3477
3478                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3479                     gameInfo.blackRating = string_to_rating(star_match[3]);
3480                     if (appData.debugMode)
3481                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3482                               gameInfo.whiteRating, gameInfo.blackRating);
3483                 }
3484                 continue;
3485             }
3486
3487             if (looking_at(buf, &i,
3488               "* * match, initial time: * minute*, increment: * second")) {
3489                 /* Header for a move list -- second line */
3490                 /* Initial board will follow if this is a wild game */
3491                 if (gameInfo.event != NULL) free(gameInfo.event);
3492                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3493                 gameInfo.event = StrSave(str);
3494                 /* [HGM] we switched variant. Translate boards if needed. */
3495                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i, "Move  ")) {
3500                 /* Beginning of a move list */
3501                 switch (ics_getting_history) {
3502                   case H_FALSE:
3503                     /* Normally should not happen */
3504                     /* Maybe user hit reset while we were parsing */
3505                     break;
3506                   case H_REQUESTED:
3507                     /* Happens if we are ignoring a move list that is not
3508                      * the one we just requested.  Common if the user
3509                      * tries to observe two games without turning off
3510                      * getMoveList */
3511                     break;
3512                   case H_GETTING_MOVES:
3513                     /* Should not happen */
3514                     DisplayError(_("Error gathering move list: nested"), 0);
3515                     ics_getting_history = H_FALSE;
3516                     break;
3517                   case H_GOT_REQ_HEADER:
3518                     ics_getting_history = H_GETTING_MOVES;
3519                     started = STARTED_MOVES;
3520                     parse_pos = 0;
3521                     if (oldi > next_out) {
3522                         SendToPlayer(&buf[next_out], oldi - next_out);
3523                     }
3524                     break;
3525                   case H_GOT_UNREQ_HEADER:
3526                     ics_getting_history = H_GETTING_MOVES;
3527                     started = STARTED_MOVES_NOHIDE;
3528                     parse_pos = 0;
3529                     break;
3530                   case H_GOT_UNWANTED_HEADER:
3531                     ics_getting_history = H_FALSE;
3532                     break;
3533                 }
3534                 continue;
3535             }
3536
3537             if (looking_at(buf, &i, "% ") ||
3538                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3539                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3540                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3541                     soughtPending = FALSE;
3542                     seekGraphUp = TRUE;
3543                     DrawSeekGraph();
3544                 }
3545                 if(suppressKibitz) next_out = i;
3546                 savingComment = FALSE;
3547                 suppressKibitz = 0;
3548                 switch (started) {
3549                   case STARTED_MOVES:
3550                   case STARTED_MOVES_NOHIDE:
3551                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3552                     parse[parse_pos + i - oldi] = NULLCHAR;
3553                     ParseGameHistory(parse);
3554 #if ZIPPY
3555                     if (appData.zippyPlay && first.initDone) {
3556                         FeedMovesToProgram(&first, forwardMostMove);
3557                         if (gameMode == IcsPlayingWhite) {
3558                             if (WhiteOnMove(forwardMostMove)) {
3559                                 if (first.sendTime) {
3560                                   if (first.useColors) {
3561                                     SendToProgram("black\n", &first);
3562                                   }
3563                                   SendTimeRemaining(&first, TRUE);
3564                                 }
3565                                 if (first.useColors) {
3566                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3567                                 }
3568                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3569                                 first.maybeThinking = TRUE;
3570                             } else {
3571                                 if (first.usePlayother) {
3572                                   if (first.sendTime) {
3573                                     SendTimeRemaining(&first, TRUE);
3574                                   }
3575                                   SendToProgram("playother\n", &first);
3576                                   firstMove = FALSE;
3577                                 } else {
3578                                   firstMove = TRUE;
3579                                 }
3580                             }
3581                         } else if (gameMode == IcsPlayingBlack) {
3582                             if (!WhiteOnMove(forwardMostMove)) {
3583                                 if (first.sendTime) {
3584                                   if (first.useColors) {
3585                                     SendToProgram("white\n", &first);
3586                                   }
3587                                   SendTimeRemaining(&first, FALSE);
3588                                 }
3589                                 if (first.useColors) {
3590                                   SendToProgram("black\n", &first);
3591                                 }
3592                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3593                                 first.maybeThinking = TRUE;
3594                             } else {
3595                                 if (first.usePlayother) {
3596                                   if (first.sendTime) {
3597                                     SendTimeRemaining(&first, FALSE);
3598                                   }
3599                                   SendToProgram("playother\n", &first);
3600                                   firstMove = FALSE;
3601                                 } else {
3602                                   firstMove = TRUE;
3603                                 }
3604                             }
3605                         }
3606                     }
3607 #endif
3608                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3609                         /* Moves came from oldmoves or moves command
3610                            while we weren't doing anything else.
3611                            */
3612                         currentMove = forwardMostMove;
3613                         ClearHighlights();/*!!could figure this out*/
3614                         flipView = appData.flipView;
3615                         DrawPosition(TRUE, boards[currentMove]);
3616                         DisplayBothClocks();
3617                         snprintf(str, MSG_SIZ, "%s vs. %s",
3618                                 gameInfo.white, gameInfo.black);
3619                         DisplayTitle(str);
3620                         gameMode = IcsIdle;
3621                     } else {
3622                         /* Moves were history of an active game */
3623                         if (gameInfo.resultDetails != NULL) {
3624                             free(gameInfo.resultDetails);
3625                             gameInfo.resultDetails = NULL;
3626                         }
3627                     }
3628                     HistorySet(parseList, backwardMostMove,
3629                                forwardMostMove, currentMove-1);
3630                     DisplayMove(currentMove - 1);
3631                     if (started == STARTED_MOVES) next_out = i;
3632                     started = STARTED_NONE;
3633                     ics_getting_history = H_FALSE;
3634                     break;
3635
3636                   case STARTED_OBSERVE:
3637                     started = STARTED_NONE;
3638                     SendToICS(ics_prefix);
3639                     SendToICS("refresh\n");
3640                     break;
3641
3642                   default:
3643                     break;
3644                 }
3645                 if(bookHit) { // [HGM] book: simulate book reply
3646                     static char bookMove[MSG_SIZ]; // a bit generous?
3647
3648                     programStats.nodes = programStats.depth = programStats.time =
3649                     programStats.score = programStats.got_only_move = 0;
3650                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3651
3652                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3653                     strcat(bookMove, bookHit);
3654                     HandleMachineMove(bookMove, &first);
3655                 }
3656                 continue;
3657             }
3658
3659             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3660                  started == STARTED_HOLDINGS ||
3661                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3662                 /* Accumulate characters in move list or board */
3663                 parse[parse_pos++] = buf[i];
3664             }
3665
3666             /* Start of game messages.  Mostly we detect start of game
3667                when the first board image arrives.  On some versions
3668                of the ICS, though, we need to do a "refresh" after starting
3669                to observe in order to get the current board right away. */
3670             if (looking_at(buf, &i, "Adding game * to observation list")) {
3671                 started = STARTED_OBSERVE;
3672                 continue;
3673             }
3674
3675             /* Handle auto-observe */
3676             if (appData.autoObserve &&
3677                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3678                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3679                 char *player;
3680                 /* Choose the player that was highlighted, if any. */
3681                 if (star_match[0][0] == '\033' ||
3682                     star_match[1][0] != '\033') {
3683                     player = star_match[0];
3684                 } else {
3685                     player = star_match[2];
3686                 }
3687                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3688                         ics_prefix, StripHighlightAndTitle(player));
3689                 SendToICS(str);
3690
3691                 /* Save ratings from notify string */
3692                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3693                 player1Rating = string_to_rating(star_match[1]);
3694                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3695                 player2Rating = string_to_rating(star_match[3]);
3696
3697                 if (appData.debugMode)
3698                   fprintf(debugFP,
3699                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3700                           player1Name, player1Rating,
3701                           player2Name, player2Rating);
3702
3703                 continue;
3704             }
3705
3706             /* Deal with automatic examine mode after a game,
3707                and with IcsObserving -> IcsExamining transition */
3708             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3709                 looking_at(buf, &i, "has made you an examiner of game *")) {
3710
3711                 int gamenum = atoi(star_match[0]);
3712                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3713                     gamenum == ics_gamenum) {
3714                     /* We were already playing or observing this game;
3715                        no need to refetch history */
3716                     gameMode = IcsExamining;
3717                     if (pausing) {
3718                         pauseExamForwardMostMove = forwardMostMove;
3719                     } else if (currentMove < forwardMostMove) {
3720                         ForwardInner(forwardMostMove);
3721                     }
3722                 } else {
3723                     /* I don't think this case really can happen */
3724                     SendToICS(ics_prefix);
3725                     SendToICS("refresh\n");
3726                 }
3727                 continue;
3728             }
3729
3730             /* Error messages */
3731 //          if (ics_user_moved) {
3732             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3733                 if (looking_at(buf, &i, "Illegal move") ||
3734                     looking_at(buf, &i, "Not a legal move") ||
3735                     looking_at(buf, &i, "Your king is in check") ||
3736                     looking_at(buf, &i, "It isn't your turn") ||
3737                     looking_at(buf, &i, "It is not your move")) {
3738                     /* Illegal move */
3739                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3740                         currentMove = forwardMostMove-1;
3741                         DisplayMove(currentMove - 1); /* before DMError */
3742                         DrawPosition(FALSE, boards[currentMove]);
3743                         SwitchClocks(forwardMostMove-1); // [HGM] race
3744                         DisplayBothClocks();
3745                     }
3746                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3747                     ics_user_moved = 0;
3748                     continue;
3749                 }
3750             }
3751
3752             if (looking_at(buf, &i, "still have time") ||
3753                 looking_at(buf, &i, "not out of time") ||
3754                 looking_at(buf, &i, "either player is out of time") ||
3755                 looking_at(buf, &i, "has timeseal; checking")) {
3756                 /* We must have called his flag a little too soon */
3757                 whiteFlag = blackFlag = FALSE;
3758                 continue;
3759             }
3760
3761             if (looking_at(buf, &i, "added * seconds to") ||
3762                 looking_at(buf, &i, "seconds were added to")) {
3763                 /* Update the clocks */
3764                 SendToICS(ics_prefix);
3765                 SendToICS("refresh\n");
3766                 continue;
3767             }
3768
3769             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3770                 ics_clock_paused = TRUE;
3771                 StopClocks();
3772                 continue;
3773             }
3774
3775             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3776                 ics_clock_paused = FALSE;
3777                 StartClocks();
3778                 continue;
3779             }
3780
3781             /* Grab player ratings from the Creating: message.
3782                Note we have to check for the special case when
3783                the ICS inserts things like [white] or [black]. */
3784             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3785                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3786                 /* star_matches:
3787                    0    player 1 name (not necessarily white)
3788                    1    player 1 rating
3789                    2    empty, white, or black (IGNORED)
3790                    3    player 2 name (not necessarily black)
3791                    4    player 2 rating
3792
3793                    The names/ratings are sorted out when the game
3794                    actually starts (below).
3795                 */
3796                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3797                 player1Rating = string_to_rating(star_match[1]);
3798                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3799                 player2Rating = string_to_rating(star_match[4]);
3800
3801                 if (appData.debugMode)
3802                   fprintf(debugFP,
3803                           "Ratings from 'Creating:' %s %d, %s %d\n",
3804                           player1Name, player1Rating,
3805                           player2Name, player2Rating);
3806
3807                 continue;
3808             }
3809
3810             /* Improved generic start/end-of-game messages */
3811             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3812                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3813                 /* If tkind == 0: */
3814                 /* star_match[0] is the game number */
3815                 /*           [1] is the white player's name */
3816                 /*           [2] is the black player's name */
3817                 /* For end-of-game: */
3818                 /*           [3] is the reason for the game end */
3819                 /*           [4] is a PGN end game-token, preceded by " " */
3820                 /* For start-of-game: */
3821                 /*           [3] begins with "Creating" or "Continuing" */
3822                 /*           [4] is " *" or empty (don't care). */
3823                 int gamenum = atoi(star_match[0]);
3824                 char *whitename, *blackname, *why, *endtoken;
3825                 ChessMove endtype = EndOfFile;
3826
3827                 if (tkind == 0) {
3828                   whitename = star_match[1];
3829                   blackname = star_match[2];
3830                   why = star_match[3];
3831                   endtoken = star_match[4];
3832                 } else {
3833                   whitename = star_match[1];
3834                   blackname = star_match[3];
3835                   why = star_match[5];
3836                   endtoken = star_match[6];
3837                 }
3838
3839                 /* Game start messages */
3840                 if (strncmp(why, "Creating ", 9) == 0 ||
3841                     strncmp(why, "Continuing ", 11) == 0) {
3842                     gs_gamenum = gamenum;
3843                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3844                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3845 #if ZIPPY
3846                     if (appData.zippyPlay) {
3847                         ZippyGameStart(whitename, blackname);
3848                     }
3849 #endif /*ZIPPY*/
3850                     partnerBoardValid = FALSE; // [HGM] bughouse
3851                     continue;
3852                 }
3853
3854                 /* Game end messages */
3855                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3856                     ics_gamenum != gamenum) {
3857                     continue;
3858                 }
3859                 while (endtoken[0] == ' ') endtoken++;
3860                 switch (endtoken[0]) {
3861                   case '*':
3862                   default:
3863                     endtype = GameUnfinished;
3864                     break;
3865                   case '0':
3866                     endtype = BlackWins;
3867                     break;
3868                   case '1':
3869                     if (endtoken[1] == '/')
3870                       endtype = GameIsDrawn;
3871                     else
3872                       endtype = WhiteWins;
3873                     break;
3874                 }
3875                 GameEnds(endtype, why, GE_ICS);
3876 #if ZIPPY
3877                 if (appData.zippyPlay && first.initDone) {
3878                     ZippyGameEnd(endtype, why);
3879                     if (first.pr == NULL) {
3880                       /* Start the next process early so that we'll
3881                          be ready for the next challenge */
3882                       StartChessProgram(&first);
3883                     }
3884                     /* Send "new" early, in case this command takes
3885                        a long time to finish, so that we'll be ready
3886                        for the next challenge. */
3887                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3888                     Reset(TRUE, TRUE);
3889                 }
3890 #endif /*ZIPPY*/
3891                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3892                 continue;
3893             }
3894
3895             if (looking_at(buf, &i, "Removing game * from observation") ||
3896                 looking_at(buf, &i, "no longer observing game *") ||
3897                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3898                 if (gameMode == IcsObserving &&
3899                     atoi(star_match[0]) == ics_gamenum)
3900                   {
3901                       /* icsEngineAnalyze */
3902                       if (appData.icsEngineAnalyze) {
3903                             ExitAnalyzeMode();
3904                             ModeHighlight();
3905                       }
3906                       StopClocks();
3907                       gameMode = IcsIdle;
3908                       ics_gamenum = -1;
3909                       ics_user_moved = FALSE;
3910                   }
3911                 continue;
3912             }
3913
3914             if (looking_at(buf, &i, "no longer examining game *")) {
3915                 if (gameMode == IcsExamining &&
3916                     atoi(star_match[0]) == ics_gamenum)
3917                   {
3918                       gameMode = IcsIdle;
3919                       ics_gamenum = -1;
3920                       ics_user_moved = FALSE;
3921                   }
3922                 continue;
3923             }
3924
3925             /* Advance leftover_start past any newlines we find,
3926                so only partial lines can get reparsed */
3927             if (looking_at(buf, &i, "\n")) {
3928                 prevColor = curColor;
3929                 if (curColor != ColorNormal) {
3930                     if (oldi > next_out) {
3931                         SendToPlayer(&buf[next_out], oldi - next_out);
3932                         next_out = oldi;
3933                     }
3934                     Colorize(ColorNormal, FALSE);
3935                     curColor = ColorNormal;
3936                 }
3937                 if (started == STARTED_BOARD) {
3938                     started = STARTED_NONE;
3939                     parse[parse_pos] = NULLCHAR;
3940                     ParseBoard12(parse);
3941                     ics_user_moved = 0;
3942
3943                     /* Send premove here */
3944                     if (appData.premove) {
3945                       char str[MSG_SIZ];
3946                       if (currentMove == 0 &&
3947                           gameMode == IcsPlayingWhite &&
3948                           appData.premoveWhite) {
3949                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3950                         if (appData.debugMode)
3951                           fprintf(debugFP, "Sending premove:\n");
3952                         SendToICS(str);
3953                       } else if (currentMove == 1 &&
3954                                  gameMode == IcsPlayingBlack &&
3955                                  appData.premoveBlack) {
3956                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3957                         if (appData.debugMode)
3958                           fprintf(debugFP, "Sending premove:\n");
3959                         SendToICS(str);
3960                       } else if (gotPremove) {
3961                         gotPremove = 0;
3962                         ClearPremoveHighlights();
3963                         if (appData.debugMode)
3964                           fprintf(debugFP, "Sending premove:\n");
3965                           UserMoveEvent(premoveFromX, premoveFromY,
3966                                         premoveToX, premoveToY,
3967                                         premovePromoChar);
3968                       }
3969                     }
3970
3971                     /* Usually suppress following prompt */
3972                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3973                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3974                         if (looking_at(buf, &i, "*% ")) {
3975                             savingComment = FALSE;
3976                             suppressKibitz = 0;
3977                         }
3978                     }
3979                     next_out = i;
3980                 } else if (started == STARTED_HOLDINGS) {
3981                     int gamenum;
3982                     char new_piece[MSG_SIZ];
3983                     started = STARTED_NONE;
3984                     parse[parse_pos] = NULLCHAR;
3985                     if (appData.debugMode)
3986                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3987                                                         parse, currentMove);
3988                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3989                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3990                         if (gameInfo.variant == VariantNormal) {
3991                           /* [HGM] We seem to switch variant during a game!
3992                            * Presumably no holdings were displayed, so we have
3993                            * to move the position two files to the right to
3994                            * create room for them!
3995                            */
3996                           VariantClass newVariant;
3997                           switch(gameInfo.boardWidth) { // base guess on board width
3998                                 case 9:  newVariant = VariantShogi; break;
3999                                 case 10: newVariant = VariantGreat; break;
4000                                 default: newVariant = VariantCrazyhouse; break;
4001                           }
4002                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4003                           /* Get a move list just to see the header, which
4004                              will tell us whether this is really bug or zh */
4005                           if (ics_getting_history == H_FALSE) {
4006                             ics_getting_history = H_REQUESTED;
4007                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4008                             SendToICS(str);
4009                           }
4010                         }
4011                         new_piece[0] = NULLCHAR;
4012                         sscanf(parse, "game %d white [%s black [%s <- %s",
4013                                &gamenum, white_holding, black_holding,
4014                                new_piece);
4015                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4016                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4017                         /* [HGM] copy holdings to board holdings area */
4018                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4019                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4020                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4021 #if ZIPPY
4022                         if (appData.zippyPlay && first.initDone) {
4023                             ZippyHoldings(white_holding, black_holding,
4024                                           new_piece);
4025                         }
4026 #endif /*ZIPPY*/
4027                         if (tinyLayout || smallLayout) {
4028                             char wh[16], bh[16];
4029                             PackHolding(wh, white_holding);
4030                             PackHolding(bh, black_holding);
4031                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4032                                     gameInfo.white, gameInfo.black);
4033                         } else {
4034                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4035                                     gameInfo.white, white_holding,
4036                                     gameInfo.black, black_holding);
4037                         }
4038                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4039                         DrawPosition(FALSE, boards[currentMove]);
4040                         DisplayTitle(str);
4041                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4042                         sscanf(parse, "game %d white [%s black [%s <- %s",
4043                                &gamenum, white_holding, black_holding,
4044                                new_piece);
4045                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4046                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4047                         /* [HGM] copy holdings to partner-board holdings area */
4048                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4049                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4050                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4051                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4052                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4053                       }
4054                     }
4055                     /* Suppress following prompt */
4056                     if (looking_at(buf, &i, "*% ")) {
4057                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4058                         savingComment = FALSE;
4059                         suppressKibitz = 0;
4060                     }
4061                     next_out = i;
4062                 }
4063                 continue;
4064             }
4065
4066             i++;                /* skip unparsed character and loop back */
4067         }
4068
4069         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4070 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4071 //          SendToPlayer(&buf[next_out], i - next_out);
4072             started != STARTED_HOLDINGS && leftover_start > next_out) {
4073             SendToPlayer(&buf[next_out], leftover_start - next_out);
4074             next_out = i;
4075         }
4076
4077         leftover_len = buf_len - leftover_start;
4078         /* if buffer ends with something we couldn't parse,
4079            reparse it after appending the next read */
4080
4081     } else if (count == 0) {
4082         RemoveInputSource(isr);
4083         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4084     } else {
4085         DisplayFatalError(_("Error reading from ICS"), error, 1);
4086     }
4087 }
4088
4089
4090 /* Board style 12 looks like this:
4091
4092    <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
4093
4094  * The "<12> " is stripped before it gets to this routine.  The two
4095  * trailing 0's (flip state and clock ticking) are later addition, and
4096  * some chess servers may not have them, or may have only the first.
4097  * Additional trailing fields may be added in the future.
4098  */
4099
4100 #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"
4101
4102 #define RELATION_OBSERVING_PLAYED    0
4103 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4104 #define RELATION_PLAYING_MYMOVE      1
4105 #define RELATION_PLAYING_NOTMYMOVE  -1
4106 #define RELATION_EXAMINING           2
4107 #define RELATION_ISOLATED_BOARD     -3
4108 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4109
4110 void
4111 ParseBoard12(string)
4112      char *string;
4113 {
4114     GameMode newGameMode;
4115     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4116     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4117     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4118     char to_play, board_chars[200];
4119     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4120     char black[32], white[32];
4121     Board board;
4122     int prevMove = currentMove;
4123     int ticking = 2;
4124     ChessMove moveType;
4125     int fromX, fromY, toX, toY;
4126     char promoChar;
4127     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4128     char *bookHit = NULL; // [HGM] book
4129     Boolean weird = FALSE, reqFlag = FALSE;
4130
4131     fromX = fromY = toX = toY = -1;
4132
4133     newGame = FALSE;
4134
4135     if (appData.debugMode)
4136       fprintf(debugFP, _("Parsing board: %s\n"), string);
4137
4138     move_str[0] = NULLCHAR;
4139     elapsed_time[0] = NULLCHAR;
4140     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4141         int  i = 0, j;
4142         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4143             if(string[i] == ' ') { ranks++; files = 0; }
4144             else files++;
4145             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4146             i++;
4147         }
4148         for(j = 0; j <i; j++) board_chars[j] = string[j];
4149         board_chars[i] = '\0';
4150         string += i + 1;
4151     }
4152     n = sscanf(string, PATTERN, &to_play, &double_push,
4153                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4154                &gamenum, white, black, &relation, &basetime, &increment,
4155                &white_stren, &black_stren, &white_time, &black_time,
4156                &moveNum, str, elapsed_time, move_str, &ics_flip,
4157                &ticking);
4158
4159     if (n < 21) {
4160         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4161         DisplayError(str, 0);
4162         return;
4163     }
4164
4165     /* Convert the move number to internal form */
4166     moveNum = (moveNum - 1) * 2;
4167     if (to_play == 'B') moveNum++;
4168     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4169       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4170                         0, 1);
4171       return;
4172     }
4173
4174     switch (relation) {
4175       case RELATION_OBSERVING_PLAYED:
4176       case RELATION_OBSERVING_STATIC:
4177         if (gamenum == -1) {
4178             /* Old ICC buglet */
4179             relation = RELATION_OBSERVING_STATIC;
4180         }
4181         newGameMode = IcsObserving;
4182         break;
4183       case RELATION_PLAYING_MYMOVE:
4184       case RELATION_PLAYING_NOTMYMOVE:
4185         newGameMode =
4186           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4187             IcsPlayingWhite : IcsPlayingBlack;
4188         break;
4189       case RELATION_EXAMINING:
4190         newGameMode = IcsExamining;
4191         break;
4192       case RELATION_ISOLATED_BOARD:
4193       default:
4194         /* Just display this board.  If user was doing something else,
4195            we will forget about it until the next board comes. */
4196         newGameMode = IcsIdle;
4197         break;
4198       case RELATION_STARTING_POSITION:
4199         newGameMode = gameMode;
4200         break;
4201     }
4202
4203     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4204          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4205       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4206       char *toSqr;
4207       for (k = 0; k < ranks; k++) {
4208         for (j = 0; j < files; j++)
4209           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4210         if(gameInfo.holdingsWidth > 1) {
4211              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4212              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4213         }
4214       }
4215       CopyBoard(partnerBoard, board);
4216       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4217         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4218         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4219       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4220       if(toSqr = strchr(str, '-')) {
4221         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4222         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4223       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4224       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4225       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4226       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4227       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4228       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4229                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4230       DisplayMessage(partnerStatus, "");
4231         partnerBoardValid = TRUE;
4232       return;
4233     }
4234
4235     /* Modify behavior for initial board display on move listing
4236        of wild games.
4237        */
4238     switch (ics_getting_history) {
4239       case H_FALSE:
4240       case H_REQUESTED:
4241         break;
4242       case H_GOT_REQ_HEADER:
4243       case H_GOT_UNREQ_HEADER:
4244         /* This is the initial position of the current game */
4245         gamenum = ics_gamenum;
4246         moveNum = 0;            /* old ICS bug workaround */
4247         if (to_play == 'B') {
4248           startedFromSetupPosition = TRUE;
4249           blackPlaysFirst = TRUE;
4250           moveNum = 1;
4251           if (forwardMostMove == 0) forwardMostMove = 1;
4252           if (backwardMostMove == 0) backwardMostMove = 1;
4253           if (currentMove == 0) currentMove = 1;
4254         }
4255         newGameMode = gameMode;
4256         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4257         break;
4258       case H_GOT_UNWANTED_HEADER:
4259         /* This is an initial board that we don't want */
4260         return;
4261       case H_GETTING_MOVES:
4262         /* Should not happen */
4263         DisplayError(_("Error gathering move list: extra board"), 0);
4264         ics_getting_history = H_FALSE;
4265         return;
4266     }
4267
4268    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4269                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4270      /* [HGM] We seem to have switched variant unexpectedly
4271       * Try to guess new variant from board size
4272       */
4273           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4274           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4275           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4276           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4277           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4278           if(!weird) newVariant = VariantNormal;
4279           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4280           /* Get a move list just to see the header, which
4281              will tell us whether this is really bug or zh */
4282           if (ics_getting_history == H_FALSE) {
4283             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4284             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4285             SendToICS(str);
4286           }
4287     }
4288
4289     /* Take action if this is the first board of a new game, or of a
4290        different game than is currently being displayed.  */
4291     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4292         relation == RELATION_ISOLATED_BOARD) {
4293
4294         /* Forget the old game and get the history (if any) of the new one */
4295         if (gameMode != BeginningOfGame) {
4296           Reset(TRUE, TRUE);
4297         }
4298         newGame = TRUE;
4299         if (appData.autoRaiseBoard) BoardToTop();
4300         prevMove = -3;
4301         if (gamenum == -1) {
4302             newGameMode = IcsIdle;
4303         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4304                    appData.getMoveList && !reqFlag) {
4305             /* Need to get game history */
4306             ics_getting_history = H_REQUESTED;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309         }
4310
4311         /* Initially flip the board to have black on the bottom if playing
4312            black or if the ICS flip flag is set, but let the user change
4313            it with the Flip View button. */
4314         flipView = appData.autoFlipView ?
4315           (newGameMode == IcsPlayingBlack) || ics_flip :
4316           appData.flipView;
4317
4318         /* Done with values from previous mode; copy in new ones */
4319         gameMode = newGameMode;
4320         ModeHighlight();
4321         ics_gamenum = gamenum;
4322         if (gamenum == gs_gamenum) {
4323             int klen = strlen(gs_kind);
4324             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4325             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4326             gameInfo.event = StrSave(str);
4327         } else {
4328             gameInfo.event = StrSave("ICS game");
4329         }
4330         gameInfo.site = StrSave(appData.icsHost);
4331         gameInfo.date = PGNDate();
4332         gameInfo.round = StrSave("-");
4333         gameInfo.white = StrSave(white);
4334         gameInfo.black = StrSave(black);
4335         timeControl = basetime * 60 * 1000;
4336         timeControl_2 = 0;
4337         timeIncrement = increment * 1000;
4338         movesPerSession = 0;
4339         gameInfo.timeControl = TimeControlTagValue();
4340         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4341   if (appData.debugMode) {
4342     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4343     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4344     setbuf(debugFP, NULL);
4345   }
4346
4347         gameInfo.outOfBook = NULL;
4348
4349         /* Do we have the ratings? */
4350         if (strcmp(player1Name, white) == 0 &&
4351             strcmp(player2Name, black) == 0) {
4352             if (appData.debugMode)
4353               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4354                       player1Rating, player2Rating);
4355             gameInfo.whiteRating = player1Rating;
4356             gameInfo.blackRating = player2Rating;
4357         } else if (strcmp(player2Name, white) == 0 &&
4358                    strcmp(player1Name, black) == 0) {
4359             if (appData.debugMode)
4360               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4361                       player2Rating, player1Rating);
4362             gameInfo.whiteRating = player2Rating;
4363             gameInfo.blackRating = player1Rating;
4364         }
4365         player1Name[0] = player2Name[0] = NULLCHAR;
4366
4367         /* Silence shouts if requested */
4368         if (appData.quietPlay &&
4369             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4370             SendToICS(ics_prefix);
4371             SendToICS("set shout 0\n");
4372         }
4373     }
4374
4375     /* Deal with midgame name changes */
4376     if (!newGame) {
4377         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4378             if (gameInfo.white) free(gameInfo.white);
4379             gameInfo.white = StrSave(white);
4380         }
4381         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4382             if (gameInfo.black) free(gameInfo.black);
4383             gameInfo.black = StrSave(black);
4384         }
4385     }
4386
4387     /* Throw away game result if anything actually changes in examine mode */
4388     if (gameMode == IcsExamining && !newGame) {
4389         gameInfo.result = GameUnfinished;
4390         if (gameInfo.resultDetails != NULL) {
4391             free(gameInfo.resultDetails);
4392             gameInfo.resultDetails = NULL;
4393         }
4394     }
4395
4396     /* In pausing && IcsExamining mode, we ignore boards coming
4397        in if they are in a different variation than we are. */
4398     if (pauseExamInvalid) return;
4399     if (pausing && gameMode == IcsExamining) {
4400         if (moveNum <= pauseExamForwardMostMove) {
4401             pauseExamInvalid = TRUE;
4402             forwardMostMove = pauseExamForwardMostMove;
4403             return;
4404         }
4405     }
4406
4407   if (appData.debugMode) {
4408     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4409   }
4410     /* Parse the board */
4411     for (k = 0; k < ranks; k++) {
4412       for (j = 0; j < files; j++)
4413         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4414       if(gameInfo.holdingsWidth > 1) {
4415            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4416            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4417       }
4418     }
4419     CopyBoard(boards[moveNum], board);
4420     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4421     if (moveNum == 0) {
4422         startedFromSetupPosition =
4423           !CompareBoards(board, initialPosition);
4424         if(startedFromSetupPosition)
4425             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4426     }
4427
4428     /* [HGM] Set castling rights. Take the outermost Rooks,
4429        to make it also work for FRC opening positions. Note that board12
4430        is really defective for later FRC positions, as it has no way to
4431        indicate which Rook can castle if they are on the same side of King.
4432        For the initial position we grant rights to the outermost Rooks,
4433        and remember thos rights, and we then copy them on positions
4434        later in an FRC game. This means WB might not recognize castlings with
4435        Rooks that have moved back to their original position as illegal,
4436        but in ICS mode that is not its job anyway.
4437     */
4438     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4439     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4440
4441         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4442             if(board[0][i] == WhiteRook) j = i;
4443         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4445             if(board[0][i] == WhiteRook) j = i;
4446         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4448             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4449         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4450         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4451             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4452         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4453
4454         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4455         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4456             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4457         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4458             if(board[BOARD_HEIGHT-1][k] == bKing)
4459                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4460         if(gameInfo.variant == VariantTwoKings) {
4461             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4462             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4463             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4464         }
4465     } else { int r;
4466         r = boards[moveNum][CASTLING][0] = initialRights[0];
4467         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4468         r = boards[moveNum][CASTLING][1] = initialRights[1];
4469         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4470         r = boards[moveNum][CASTLING][3] = initialRights[3];
4471         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4472         r = boards[moveNum][CASTLING][4] = initialRights[4];
4473         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4474         /* wildcastle kludge: always assume King has rights */
4475         r = boards[moveNum][CASTLING][2] = initialRights[2];
4476         r = boards[moveNum][CASTLING][5] = initialRights[5];
4477     }
4478     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4479     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4480
4481
4482     if (ics_getting_history == H_GOT_REQ_HEADER ||
4483         ics_getting_history == H_GOT_UNREQ_HEADER) {
4484         /* This was an initial position from a move list, not
4485            the current position */
4486         return;
4487     }
4488
4489     /* Update currentMove and known move number limits */
4490     newMove = newGame || moveNum > forwardMostMove;
4491
4492     if (newGame) {
4493         forwardMostMove = backwardMostMove = currentMove = moveNum;
4494         if (gameMode == IcsExamining && moveNum == 0) {
4495           /* Workaround for ICS limitation: we are not told the wild
4496              type when starting to examine a game.  But if we ask for
4497              the move list, the move list header will tell us */
4498             ics_getting_history = H_REQUESTED;
4499             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4500             SendToICS(str);
4501         }
4502     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4503                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4504 #if ZIPPY
4505         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4506         /* [HGM] applied this also to an engine that is silently watching        */
4507         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4508             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4509             gameInfo.variant == currentlyInitializedVariant) {
4510           takeback = forwardMostMove - moveNum;
4511           for (i = 0; i < takeback; i++) {
4512             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4513             SendToProgram("undo\n", &first);
4514           }
4515         }
4516 #endif
4517
4518         forwardMostMove = moveNum;
4519         if (!pausing || currentMove > forwardMostMove)
4520           currentMove = forwardMostMove;
4521     } else {
4522         /* New part of history that is not contiguous with old part */
4523         if (pausing && gameMode == IcsExamining) {
4524             pauseExamInvalid = TRUE;
4525             forwardMostMove = pauseExamForwardMostMove;
4526             return;
4527         }
4528         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4529 #if ZIPPY
4530             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4531                 // [HGM] when we will receive the move list we now request, it will be
4532                 // fed to the engine from the first move on. So if the engine is not
4533                 // in the initial position now, bring it there.
4534                 InitChessProgram(&first, 0);
4535             }
4536 #endif
4537             ics_getting_history = H_REQUESTED;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540         }
4541         forwardMostMove = backwardMostMove = currentMove = moveNum;
4542     }
4543
4544     /* Update the clocks */
4545     if (strchr(elapsed_time, '.')) {
4546       /* Time is in ms */
4547       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4548       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4549     } else {
4550       /* Time is in seconds */
4551       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4552       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4553     }
4554
4555
4556 #if ZIPPY
4557     if (appData.zippyPlay && newGame &&
4558         gameMode != IcsObserving && gameMode != IcsIdle &&
4559         gameMode != IcsExamining)
4560       ZippyFirstBoard(moveNum, basetime, increment);
4561 #endif
4562
4563     /* Put the move on the move list, first converting
4564        to canonical algebraic form. */
4565     if (moveNum > 0) {
4566   if (appData.debugMode) {
4567     if (appData.debugMode) { int f = forwardMostMove;
4568         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4569                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4570                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4571     }
4572     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4573     fprintf(debugFP, "moveNum = %d\n", moveNum);
4574     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4575     setbuf(debugFP, NULL);
4576   }
4577         if (moveNum <= backwardMostMove) {
4578             /* We don't know what the board looked like before
4579                this move.  Punt. */
4580           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4581             strcat(parseList[moveNum - 1], " ");
4582             strcat(parseList[moveNum - 1], elapsed_time);
4583             moveList[moveNum - 1][0] = NULLCHAR;
4584         } else if (strcmp(move_str, "none") == 0) {
4585             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4586             /* Again, we don't know what the board looked like;
4587                this is really the start of the game. */
4588             parseList[moveNum - 1][0] = NULLCHAR;
4589             moveList[moveNum - 1][0] = NULLCHAR;
4590             backwardMostMove = moveNum;
4591             startedFromSetupPosition = TRUE;
4592             fromX = fromY = toX = toY = -1;
4593         } else {
4594           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4595           //                 So we parse the long-algebraic move string in stead of the SAN move
4596           int valid; char buf[MSG_SIZ], *prom;
4597
4598           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4599                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4600           // str looks something like "Q/a1-a2"; kill the slash
4601           if(str[1] == '/')
4602             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4603           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4604           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4605                 strcat(buf, prom); // long move lacks promo specification!
4606           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4607                 if(appData.debugMode)
4608                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4609                 safeStrCpy(move_str, buf, MSG_SIZ);
4610           }
4611           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4612                                 &fromX, &fromY, &toX, &toY, &promoChar)
4613                || ParseOneMove(buf, moveNum - 1, &moveType,
4614                                 &fromX, &fromY, &toX, &toY, &promoChar);
4615           // end of long SAN patch
4616           if (valid) {
4617             (void) CoordsToAlgebraic(boards[moveNum - 1],
4618                                      PosFlags(moveNum - 1),
4619                                      fromY, fromX, toY, toX, promoChar,
4620                                      parseList[moveNum-1]);
4621             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4622               case MT_NONE:
4623               case MT_STALEMATE:
4624               default:
4625                 break;
4626               case MT_CHECK:
4627                 if(gameInfo.variant != VariantShogi)
4628                     strcat(parseList[moveNum - 1], "+");
4629                 break;
4630               case MT_CHECKMATE:
4631               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4632                 strcat(parseList[moveNum - 1], "#");
4633                 break;
4634             }
4635             strcat(parseList[moveNum - 1], " ");
4636             strcat(parseList[moveNum - 1], elapsed_time);
4637             /* currentMoveString is set as a side-effect of ParseOneMove */
4638             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4639             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4640             strcat(moveList[moveNum - 1], "\n");
4641
4642             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4643                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4644               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4645                 ChessSquare old, new = boards[moveNum][k][j];
4646                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4647                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4648                   if(old == new) continue;
4649                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4650                   else if(new == WhiteWazir || new == BlackWazir) {
4651                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4652                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4653                       else boards[moveNum][k][j] = old; // preserve type of Gold
4654                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4655                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4656               }
4657           } else {
4658             /* Move from ICS was illegal!?  Punt. */
4659             if (appData.debugMode) {
4660               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4661               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4662             }
4663             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4664             strcat(parseList[moveNum - 1], " ");
4665             strcat(parseList[moveNum - 1], elapsed_time);
4666             moveList[moveNum - 1][0] = NULLCHAR;
4667             fromX = fromY = toX = toY = -1;
4668           }
4669         }
4670   if (appData.debugMode) {
4671     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4672     setbuf(debugFP, NULL);
4673   }
4674
4675 #if ZIPPY
4676         /* Send move to chess program (BEFORE animating it). */
4677         if (appData.zippyPlay && !newGame && newMove &&
4678            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4679
4680             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4681                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4682                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4683                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4684                             move_str);
4685                     DisplayError(str, 0);
4686                 } else {
4687                     if (first.sendTime) {
4688                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4689                     }
4690                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4691                     if (firstMove && !bookHit) {
4692                         firstMove = FALSE;
4693                         if (first.useColors) {
4694                           SendToProgram(gameMode == IcsPlayingWhite ?
4695                                         "white\ngo\n" :
4696                                         "black\ngo\n", &first);
4697                         } else {
4698                           SendToProgram("go\n", &first);
4699                         }
4700                         first.maybeThinking = TRUE;
4701                     }
4702                 }
4703             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4704               if (moveList[moveNum - 1][0] == NULLCHAR) {
4705                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4706                 DisplayError(str, 0);
4707               } else {
4708                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4709                 SendMoveToProgram(moveNum - 1, &first);
4710               }
4711             }
4712         }
4713 #endif
4714     }
4715
4716     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4717         /* If move comes from a remote source, animate it.  If it
4718            isn't remote, it will have already been animated. */
4719         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4720             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4721         }
4722         if (!pausing && appData.highlightLastMove) {
4723             SetHighlights(fromX, fromY, toX, toY);
4724         }
4725     }
4726
4727     /* Start the clocks */
4728     whiteFlag = blackFlag = FALSE;
4729     appData.clockMode = !(basetime == 0 && increment == 0);
4730     if (ticking == 0) {
4731       ics_clock_paused = TRUE;
4732       StopClocks();
4733     } else if (ticking == 1) {
4734       ics_clock_paused = FALSE;
4735     }
4736     if (gameMode == IcsIdle ||
4737         relation == RELATION_OBSERVING_STATIC ||
4738         relation == RELATION_EXAMINING ||
4739         ics_clock_paused)
4740       DisplayBothClocks();
4741     else
4742       StartClocks();
4743
4744     /* Display opponents and material strengths */
4745     if (gameInfo.variant != VariantBughouse &&
4746         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4747         if (tinyLayout || smallLayout) {
4748             if(gameInfo.variant == VariantNormal)
4749               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4750                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4751                     basetime, increment);
4752             else
4753               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4754                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4755                     basetime, increment, (int) gameInfo.variant);
4756         } else {
4757             if(gameInfo.variant == VariantNormal)
4758               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4759                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4760                     basetime, increment);
4761             else
4762               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4763                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4764                     basetime, increment, VariantName(gameInfo.variant));
4765         }
4766         DisplayTitle(str);
4767   if (appData.debugMode) {
4768     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4769   }
4770     }
4771
4772
4773     /* Display the board */
4774     if (!pausing && !appData.noGUI) {
4775
4776       if (appData.premove)
4777           if (!gotPremove ||
4778              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4779              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4780               ClearPremoveHighlights();
4781
4782       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4783         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4784       DrawPosition(j, boards[currentMove]);
4785
4786       DisplayMove(moveNum - 1);
4787       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4788             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4789               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4790         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4791       }
4792     }
4793
4794     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4795 #if ZIPPY
4796     if(bookHit) { // [HGM] book: simulate book reply
4797         static char bookMove[MSG_SIZ]; // a bit generous?
4798
4799         programStats.nodes = programStats.depth = programStats.time =
4800         programStats.score = programStats.got_only_move = 0;
4801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4802
4803         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4804         strcat(bookMove, bookHit);
4805         HandleMachineMove(bookMove, &first);
4806     }
4807 #endif
4808 }
4809
4810 void
4811 GetMoveListEvent()
4812 {
4813     char buf[MSG_SIZ];
4814     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4815         ics_getting_history = H_REQUESTED;
4816         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4817         SendToICS(buf);
4818     }
4819 }
4820
4821 void
4822 AnalysisPeriodicEvent(force)
4823      int force;
4824 {
4825     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4826          && !force) || !appData.periodicUpdates)
4827       return;
4828
4829     /* Send . command to Crafty to collect stats */
4830     SendToProgram(".\n", &first);
4831
4832     /* Don't send another until we get a response (this makes
4833        us stop sending to old Crafty's which don't understand
4834        the "." command (sending illegal cmds resets node count & time,
4835        which looks bad)) */
4836     programStats.ok_to_send = 0;
4837 }
4838
4839 void ics_update_width(new_width)
4840         int new_width;
4841 {
4842         ics_printf("set width %d\n", new_width);
4843 }
4844
4845 void
4846 SendMoveToProgram(moveNum, cps)
4847      int moveNum;
4848      ChessProgramState *cps;
4849 {
4850     char buf[MSG_SIZ];
4851
4852     if (cps->useUsermove) {
4853       SendToProgram("usermove ", cps);
4854     }
4855     if (cps->useSAN) {
4856       char *space;
4857       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4858         int len = space - parseList[moveNum];
4859         memcpy(buf, parseList[moveNum], len);
4860         buf[len++] = '\n';
4861         buf[len] = NULLCHAR;
4862       } else {
4863         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4864       }
4865       SendToProgram(buf, cps);
4866     } else {
4867       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4868         AlphaRank(moveList[moveNum], 4);
4869         SendToProgram(moveList[moveNum], cps);
4870         AlphaRank(moveList[moveNum], 4); // and back
4871       } else
4872       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4873        * the engine. It would be nice to have a better way to identify castle
4874        * moves here. */
4875       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4876                                                                          && cps->useOOCastle) {
4877         int fromX = moveList[moveNum][0] - AAA;
4878         int fromY = moveList[moveNum][1] - ONE;
4879         int toX = moveList[moveNum][2] - AAA;
4880         int toY = moveList[moveNum][3] - ONE;
4881         if((boards[moveNum][fromY][fromX] == WhiteKing
4882             && boards[moveNum][toY][toX] == WhiteRook)
4883            || (boards[moveNum][fromY][fromX] == BlackKing
4884                && boards[moveNum][toY][toX] == BlackRook)) {
4885           if(toX > fromX) SendToProgram("O-O\n", cps);
4886           else SendToProgram("O-O-O\n", cps);
4887         }
4888         else SendToProgram(moveList[moveNum], cps);
4889       }
4890       else SendToProgram(moveList[moveNum], cps);
4891       /* End of additions by Tord */
4892     }
4893
4894     /* [HGM] setting up the opening has brought engine in force mode! */
4895     /*       Send 'go' if we are in a mode where machine should play. */
4896     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4897         (gameMode == TwoMachinesPlay   ||
4898 #if ZIPPY
4899          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4900 #endif
4901          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4902         SendToProgram("go\n", cps);
4903   if (appData.debugMode) {
4904     fprintf(debugFP, "(extra)\n");
4905   }
4906     }
4907     setboardSpoiledMachineBlack = 0;
4908 }
4909
4910 void
4911 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4912      ChessMove moveType;
4913      int fromX, fromY, toX, toY;
4914      char promoChar;
4915 {
4916     char user_move[MSG_SIZ];
4917
4918     switch (moveType) {
4919       default:
4920         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4921                 (int)moveType, fromX, fromY, toX, toY);
4922         DisplayError(user_move + strlen("say "), 0);
4923         break;
4924       case WhiteKingSideCastle:
4925       case BlackKingSideCastle:
4926       case WhiteQueenSideCastleWild:
4927       case BlackQueenSideCastleWild:
4928       /* PUSH Fabien */
4929       case WhiteHSideCastleFR:
4930       case BlackHSideCastleFR:
4931       /* POP Fabien */
4932         snprintf(user_move, MSG_SIZ, "o-o\n");
4933         break;
4934       case WhiteQueenSideCastle:
4935       case BlackQueenSideCastle:
4936       case WhiteKingSideCastleWild:
4937       case BlackKingSideCastleWild:
4938       /* PUSH Fabien */
4939       case WhiteASideCastleFR:
4940       case BlackASideCastleFR:
4941       /* POP Fabien */
4942         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4943         break;
4944       case WhiteNonPromotion:
4945       case BlackNonPromotion:
4946         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4947         break;
4948       case WhitePromotion:
4949       case BlackPromotion:
4950         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4951           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4952                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4953                 PieceToChar(WhiteFerz));
4954         else if(gameInfo.variant == VariantGreat)
4955           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4956                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4957                 PieceToChar(WhiteMan));
4958         else
4959           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4960                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4961                 promoChar);
4962         break;
4963       case WhiteDrop:
4964       case BlackDrop:
4965       drop:
4966         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4967                  ToUpper(PieceToChar((ChessSquare) fromX)),
4968                  AAA + toX, ONE + toY);
4969         break;
4970       case IllegalMove:  /* could be a variant we don't quite understand */
4971         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4972       case NormalMove:
4973       case WhiteCapturesEnPassant:
4974       case BlackCapturesEnPassant:
4975         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4977         break;
4978     }
4979     SendToICS(user_move);
4980     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4981         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4982 }
4983
4984 void
4985 UploadGameEvent()
4986 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4987     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4988     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4989     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4990         DisplayError("You cannot do this while you are playing or observing", 0);
4991         return;
4992     }
4993     if(gameMode != IcsExamining) { // is this ever not the case?
4994         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4995
4996         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4997           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4998         } else { // on FICS we must first go to general examine mode
4999           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5000         }
5001         if(gameInfo.variant != VariantNormal) {
5002             // try figure out wild number, as xboard names are not always valid on ICS
5003             for(i=1; i<=36; i++) {
5004               snprintf(buf, MSG_SIZ, "wild/%d", i);
5005                 if(StringToVariant(buf) == gameInfo.variant) break;
5006             }
5007             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5008             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5009             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5010         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5011         SendToICS(ics_prefix);
5012         SendToICS(buf);
5013         if(startedFromSetupPosition || backwardMostMove != 0) {
5014           fen = PositionToFEN(backwardMostMove, NULL);
5015           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5016             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5017             SendToICS(buf);
5018           } else { // FICS: everything has to set by separate bsetup commands
5019             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5020             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5021             SendToICS(buf);
5022             if(!WhiteOnMove(backwardMostMove)) {
5023                 SendToICS("bsetup tomove black\n");
5024             }
5025             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5026             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5027             SendToICS(buf);
5028             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5029             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5030             SendToICS(buf);
5031             i = boards[backwardMostMove][EP_STATUS];
5032             if(i >= 0) { // set e.p.
5033               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5034                 SendToICS(buf);
5035             }
5036             bsetup++;
5037           }
5038         }
5039       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5040             SendToICS("bsetup done\n"); // switch to normal examining.
5041     }
5042     for(i = backwardMostMove; i<last; i++) {
5043         char buf[20];
5044         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5045         SendToICS(buf);
5046     }
5047     SendToICS(ics_prefix);
5048     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5049 }
5050
5051 void
5052 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5053      int rf, ff, rt, ft;
5054      char promoChar;
5055      char move[7];
5056 {
5057     if (rf == DROP_RANK) {
5058       sprintf(move, "%c@%c%c\n",
5059                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5060     } else {
5061         if (promoChar == 'x' || promoChar == NULLCHAR) {
5062           sprintf(move, "%c%c%c%c\n",
5063                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5064         } else {
5065             sprintf(move, "%c%c%c%c%c\n",
5066                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5067         }
5068     }
5069 }
5070
5071 void
5072 ProcessICSInitScript(f)
5073      FILE *f;
5074 {
5075     char buf[MSG_SIZ];
5076
5077     while (fgets(buf, MSG_SIZ, f)) {
5078         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5079     }
5080
5081     fclose(f);
5082 }
5083
5084
5085 static int lastX, lastY, selectFlag, dragging;
5086
5087 void
5088 Sweep(int step)
5089 {
5090     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5091     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5092     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5093     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5094     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5095     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5096     do {
5097         promoSweep -= step;
5098         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5099         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5100         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5101         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5102         if(!step) step = 1;
5103     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5104             appData.testLegality && (promoSweep == king ||
5105             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5106     ChangeDragPiece(promoSweep);
5107 }
5108
5109 int PromoScroll(int x, int y)
5110 {
5111   int step = 0;
5112
5113   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5114   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5115   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5116   if(!step) return FALSE;
5117   lastX = x; lastY = y;
5118   if((promoSweep < BlackPawn) == flipView) step = -step;
5119   if(step > 0) selectFlag = 1;
5120   if(!selectFlag) Sweep(step);
5121   return FALSE;
5122 }
5123
5124 void
5125 NextPiece(int step)
5126 {
5127     ChessSquare piece = boards[currentMove][toY][toX];
5128     do {
5129         pieceSweep -= step;
5130         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5131         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5132         if(!step) step = -1;
5133     } while(PieceToChar(pieceSweep) == '.');
5134     boards[currentMove][toY][toX] = pieceSweep;
5135     DrawPosition(FALSE, boards[currentMove]);
5136     boards[currentMove][toY][toX] = piece;
5137 }
5138 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5139 void
5140 AlphaRank(char *move, int n)
5141 {
5142 //    char *p = move, c; int x, y;
5143
5144     if (appData.debugMode) {
5145         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5146     }
5147
5148     if(move[1]=='*' &&
5149        move[2]>='0' && move[2]<='9' &&
5150        move[3]>='a' && move[3]<='x'    ) {
5151         move[1] = '@';
5152         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5153         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5154     } else
5155     if(move[0]>='0' && move[0]<='9' &&
5156        move[1]>='a' && move[1]<='x' &&
5157        move[2]>='0' && move[2]<='9' &&
5158        move[3]>='a' && move[3]<='x'    ) {
5159         /* input move, Shogi -> normal */
5160         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5161         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5162         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5163         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5164     } else
5165     if(move[1]=='@' &&
5166        move[3]>='0' && move[3]<='9' &&
5167        move[2]>='a' && move[2]<='x'    ) {
5168         move[1] = '*';
5169         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5170         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5171     } else
5172     if(
5173        move[0]>='a' && move[0]<='x' &&
5174        move[3]>='0' && move[3]<='9' &&
5175        move[2]>='a' && move[2]<='x'    ) {
5176          /* output move, normal -> Shogi */
5177         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5178         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5179         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5180         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5181         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5182     }
5183     if (appData.debugMode) {
5184         fprintf(debugFP, "   out = '%s'\n", move);
5185     }
5186 }
5187
5188 char yy_textstr[8000];
5189
5190 /* Parser for moves from gnuchess, ICS, or user typein box */
5191 Boolean
5192 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5193      char *move;
5194      int moveNum;
5195      ChessMove *moveType;
5196      int *fromX, *fromY, *toX, *toY;
5197      char *promoChar;
5198 {
5199     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5200
5201     switch (*moveType) {
5202       case WhitePromotion:
5203       case BlackPromotion:
5204       case WhiteNonPromotion:
5205       case BlackNonPromotion:
5206       case NormalMove:
5207       case WhiteCapturesEnPassant:
5208       case BlackCapturesEnPassant:
5209       case WhiteKingSideCastle:
5210       case WhiteQueenSideCastle:
5211       case BlackKingSideCastle:
5212       case BlackQueenSideCastle:
5213       case WhiteKingSideCastleWild:
5214       case WhiteQueenSideCastleWild:
5215       case BlackKingSideCastleWild:
5216       case BlackQueenSideCastleWild:
5217       /* Code added by Tord: */
5218       case WhiteHSideCastleFR:
5219       case WhiteASideCastleFR:
5220       case BlackHSideCastleFR:
5221       case BlackASideCastleFR:
5222       /* End of code added by Tord */
5223       case IllegalMove:         /* bug or odd chess variant */
5224         *fromX = currentMoveString[0] - AAA;
5225         *fromY = currentMoveString[1] - ONE;
5226         *toX = currentMoveString[2] - AAA;
5227         *toY = currentMoveString[3] - ONE;
5228         *promoChar = currentMoveString[4];
5229         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5230             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5231     if (appData.debugMode) {
5232         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5233     }
5234             *fromX = *fromY = *toX = *toY = 0;
5235             return FALSE;
5236         }
5237         if (appData.testLegality) {
5238           return (*moveType != IllegalMove);
5239         } else {
5240           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5241                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5242         }
5243
5244       case WhiteDrop:
5245       case BlackDrop:
5246         *fromX = *moveType == WhiteDrop ?
5247           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5248           (int) CharToPiece(ToLower(currentMoveString[0]));
5249         *fromY = DROP_RANK;
5250         *toX = currentMoveString[2] - AAA;
5251         *toY = currentMoveString[3] - ONE;
5252         *promoChar = NULLCHAR;
5253         return TRUE;
5254
5255       case AmbiguousMove:
5256       case ImpossibleMove:
5257       case EndOfFile:
5258       case ElapsedTime:
5259       case Comment:
5260       case PGNTag:
5261       case NAG:
5262       case WhiteWins:
5263       case BlackWins:
5264       case GameIsDrawn:
5265       default:
5266     if (appData.debugMode) {
5267         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5268     }
5269         /* bug? */
5270         *fromX = *fromY = *toX = *toY = 0;
5271         *promoChar = NULLCHAR;
5272         return FALSE;
5273     }
5274 }
5275
5276 Boolean pushed = FALSE;
5277
5278 void
5279 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5280 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5281   int fromX, fromY, toX, toY; char promoChar;
5282   ChessMove moveType;
5283   Boolean valid;
5284   int nr = 0;
5285
5286   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5287     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5288     pushed = TRUE;
5289   }
5290   endPV = forwardMostMove;
5291   do {
5292     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5293     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5294     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5295 if(appData.debugMode){
5296 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);
5297 }
5298     if(!valid && nr == 0 &&
5299        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5300         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5301         // Hande case where played move is different from leading PV move
5302         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5303         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5304         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5305         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5306           endPV += 2; // if position different, keep this
5307           moveList[endPV-1][0] = fromX + AAA;
5308           moveList[endPV-1][1] = fromY + ONE;
5309           moveList[endPV-1][2] = toX + AAA;
5310           moveList[endPV-1][3] = toY + ONE;
5311           parseList[endPV-1][0] = NULLCHAR;
5312           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5313         }
5314       }
5315     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5316     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5317     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5318     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5319         valid++; // allow comments in PV
5320         continue;
5321     }
5322     nr++;
5323     if(endPV+1 > framePtr) break; // no space, truncate
5324     if(!valid) break;
5325     endPV++;
5326     CopyBoard(boards[endPV], boards[endPV-1]);
5327     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5328     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5329     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5330     CoordsToAlgebraic(boards[endPV - 1],
5331                              PosFlags(endPV - 1),
5332                              fromY, fromX, toY, toX, promoChar,
5333                              parseList[endPV - 1]);
5334   } while(valid);
5335   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5336   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5337   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5338                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5339   DrawPosition(TRUE, boards[currentMove]);
5340 }
5341
5342 int
5343 MultiPV(ChessProgramState *cps)
5344 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5345         int i;
5346         for(i=0; i<cps->nrOptions; i++)
5347             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5348                 return i;
5349         return -1;
5350 }
5351
5352 Boolean
5353 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5354 {
5355         int startPV, multi, lineStart, origIndex = index;
5356         char *p, buf2[MSG_SIZ];
5357
5358         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5359         lastX = x; lastY = y;
5360         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5361         lineStart = startPV = index;
5362         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5363         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5364         index = startPV;
5365         do{ while(buf[index] && buf[index] != '\n') index++;
5366         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5367         buf[index] = 0;
5368         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5369                 int n = first.option[multi].value;
5370                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5371                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5372                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5373                 first.option[multi].value = n;
5374                 *start = *end = 0;
5375                 return FALSE;
5376         }
5377         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5378         *start = startPV; *end = index-1;
5379         return TRUE;
5380 }
5381
5382 Boolean
5383 LoadPV(int x, int y)
5384 { // called on right mouse click to load PV
5385   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5386   lastX = x; lastY = y;
5387   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5388   return TRUE;
5389 }
5390
5391 void
5392 UnLoadPV()
5393 {
5394   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5395   if(endPV < 0) return;
5396   endPV = -1;
5397   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5398         Boolean saveAnimate = appData.animate;
5399         if(pushed) {
5400             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5401                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5402             } else storedGames--; // abandon shelved tail of original game
5403         }
5404         pushed = FALSE;
5405         forwardMostMove = currentMove;
5406         currentMove = oldFMM;
5407         appData.animate = FALSE;
5408         ToNrEvent(forwardMostMove);
5409         appData.animate = saveAnimate;
5410   }
5411   currentMove = forwardMostMove;
5412   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5413   ClearPremoveHighlights();
5414   DrawPosition(TRUE, boards[currentMove]);
5415 }
5416
5417 void
5418 MovePV(int x, int y, int h)
5419 { // step through PV based on mouse coordinates (called on mouse move)
5420   int margin = h>>3, step = 0;
5421
5422   // we must somehow check if right button is still down (might be released off board!)
5423   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5424   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5425   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5426   if(!step) return;
5427   lastX = x; lastY = y;
5428
5429   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5430   if(endPV < 0) return;
5431   if(y < margin) step = 1; else
5432   if(y > h - margin) step = -1;
5433   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5434   currentMove += step;
5435   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5436   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5437                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5438   DrawPosition(FALSE, boards[currentMove]);
5439 }
5440
5441
5442 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5443 // All positions will have equal probability, but the current method will not provide a unique
5444 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5445 #define DARK 1
5446 #define LITE 2
5447 #define ANY 3
5448
5449 int squaresLeft[4];
5450 int piecesLeft[(int)BlackPawn];
5451 int seed, nrOfShuffles;
5452
5453 void GetPositionNumber()
5454 {       // sets global variable seed
5455         int i;
5456
5457         seed = appData.defaultFrcPosition;
5458         if(seed < 0) { // randomize based on time for negative FRC position numbers
5459                 for(i=0; i<50; i++) seed += random();
5460                 seed = random() ^ random() >> 8 ^ random() << 8;
5461                 if(seed<0) seed = -seed;
5462         }
5463 }
5464
5465 int put(Board board, int pieceType, int rank, int n, int shade)
5466 // put the piece on the (n-1)-th empty squares of the given shade
5467 {
5468         int i;
5469
5470         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5471                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5472                         board[rank][i] = (ChessSquare) pieceType;
5473                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5474                         squaresLeft[ANY]--;
5475                         piecesLeft[pieceType]--;
5476                         return i;
5477                 }
5478         }
5479         return -1;
5480 }
5481
5482
5483 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5484 // calculate where the next piece goes, (any empty square), and put it there
5485 {
5486         int i;
5487
5488         i = seed % squaresLeft[shade];
5489         nrOfShuffles *= squaresLeft[shade];
5490         seed /= squaresLeft[shade];
5491         put(board, pieceType, rank, i, shade);
5492 }
5493
5494 void AddTwoPieces(Board board, int pieceType, int rank)
5495 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5496 {
5497         int i, n=squaresLeft[ANY], j=n-1, k;
5498
5499         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5500         i = seed % k;  // pick one
5501         nrOfShuffles *= k;
5502         seed /= k;
5503         while(i >= j) i -= j--;
5504         j = n - 1 - j; i += j;
5505         put(board, pieceType, rank, j, ANY);
5506         put(board, pieceType, rank, i, ANY);
5507 }
5508
5509 void SetUpShuffle(Board board, int number)
5510 {
5511         int i, p, first=1;
5512
5513         GetPositionNumber(); nrOfShuffles = 1;
5514
5515         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5516         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5517         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5518
5519         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5520
5521         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5522             p = (int) board[0][i];
5523             if(p < (int) BlackPawn) piecesLeft[p] ++;
5524             board[0][i] = EmptySquare;
5525         }
5526
5527         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5528             // shuffles restricted to allow normal castling put KRR first
5529             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5530                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5531             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5532                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5533             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5534                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5535             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5536                 put(board, WhiteRook, 0, 0, ANY);
5537             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5538         }
5539
5540         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5541             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5542             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5543                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5544                 while(piecesLeft[p] >= 2) {
5545                     AddOnePiece(board, p, 0, LITE);
5546                     AddOnePiece(board, p, 0, DARK);
5547                 }
5548                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5549             }
5550
5551         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5552             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5553             // but we leave King and Rooks for last, to possibly obey FRC restriction
5554             if(p == (int)WhiteRook) continue;
5555             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5556             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5557         }
5558
5559         // now everything is placed, except perhaps King (Unicorn) and Rooks
5560
5561         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5562             // Last King gets castling rights
5563             while(piecesLeft[(int)WhiteUnicorn]) {
5564                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5565                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5566             }
5567
5568             while(piecesLeft[(int)WhiteKing]) {
5569                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5570                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5571             }
5572
5573
5574         } else {
5575             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5576             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5577         }
5578
5579         // Only Rooks can be left; simply place them all
5580         while(piecesLeft[(int)WhiteRook]) {
5581                 i = put(board, WhiteRook, 0, 0, ANY);
5582                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5583                         if(first) {
5584                                 first=0;
5585                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5586                         }
5587                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5588                 }
5589         }
5590         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5591             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5592         }
5593
5594         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5595 }
5596
5597 int SetCharTable( char *table, const char * map )
5598 /* [HGM] moved here from winboard.c because of its general usefulness */
5599 /*       Basically a safe strcpy that uses the last character as King */
5600 {
5601     int result = FALSE; int NrPieces;
5602
5603     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5604                     && NrPieces >= 12 && !(NrPieces&1)) {
5605         int i; /* [HGM] Accept even length from 12 to 34 */
5606
5607         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5608         for( i=0; i<NrPieces/2-1; i++ ) {
5609             table[i] = map[i];
5610             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5611         }
5612         table[(int) WhiteKing]  = map[NrPieces/2-1];
5613         table[(int) BlackKing]  = map[NrPieces-1];
5614
5615         result = TRUE;
5616     }
5617
5618     return result;
5619 }
5620
5621 void Prelude(Board board)
5622 {       // [HGM] superchess: random selection of exo-pieces
5623         int i, j, k; ChessSquare p;
5624         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5625
5626         GetPositionNumber(); // use FRC position number
5627
5628         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5629             SetCharTable(pieceToChar, appData.pieceToCharTable);
5630             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5631                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5632         }
5633
5634         j = seed%4;                 seed /= 4;
5635         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5636         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5637         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5638         j = seed%3 + (seed%3 >= j); seed /= 3;
5639         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5640         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5641         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5642         j = seed%3;                 seed /= 3;
5643         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5644         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5645         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5646         j = seed%2 + (seed%2 >= j); seed /= 2;
5647         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5648         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5649         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5650         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5651         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5652         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5653         put(board, exoPieces[0],    0, 0, ANY);
5654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5655 }
5656
5657 void
5658 InitPosition(redraw)
5659      int redraw;
5660 {
5661     ChessSquare (* pieces)[BOARD_FILES];
5662     int i, j, pawnRow, overrule,
5663     oldx = gameInfo.boardWidth,
5664     oldy = gameInfo.boardHeight,
5665     oldh = gameInfo.holdingsWidth;
5666     static int oldv;
5667
5668     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5669
5670     /* [AS] Initialize pv info list [HGM] and game status */
5671     {
5672         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5673             pvInfoList[i].depth = 0;
5674             boards[i][EP_STATUS] = EP_NONE;
5675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5676         }
5677
5678         initialRulePlies = 0; /* 50-move counter start */
5679
5680         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5681         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5682     }
5683
5684
5685     /* [HGM] logic here is completely changed. In stead of full positions */
5686     /* the initialized data only consist of the two backranks. The switch */
5687     /* selects which one we will use, which is than copied to the Board   */
5688     /* initialPosition, which for the rest is initialized by Pawns and    */
5689     /* empty squares. This initial position is then copied to boards[0],  */
5690     /* possibly after shuffling, so that it remains available.            */
5691
5692     gameInfo.holdingsWidth = 0; /* default board sizes */
5693     gameInfo.boardWidth    = 8;
5694     gameInfo.boardHeight   = 8;
5695     gameInfo.holdingsSize  = 0;
5696     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5697     for(i=0; i<BOARD_FILES-2; i++)
5698       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5699     initialPosition[EP_STATUS] = EP_NONE;
5700     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5701     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5702          SetCharTable(pieceNickName, appData.pieceNickNames);
5703     else SetCharTable(pieceNickName, "............");
5704     pieces = FIDEArray;
5705
5706     switch (gameInfo.variant) {
5707     case VariantFischeRandom:
5708       shuffleOpenings = TRUE;
5709     default:
5710       break;
5711     case VariantShatranj:
5712       pieces = ShatranjArray;
5713       nrCastlingRights = 0;
5714       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5715       break;
5716     case VariantMakruk:
5717       pieces = makrukArray;
5718       nrCastlingRights = 0;
5719       startedFromSetupPosition = TRUE;
5720       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5721       break;
5722     case VariantTwoKings:
5723       pieces = twoKingsArray;
5724       break;
5725     case VariantCapaRandom:
5726       shuffleOpenings = TRUE;
5727     case VariantCapablanca:
5728       pieces = CapablancaArray;
5729       gameInfo.boardWidth = 10;
5730       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5731       break;
5732     case VariantGothic:
5733       pieces = GothicArray;
5734       gameInfo.boardWidth = 10;
5735       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5736       break;
5737     case VariantSChess:
5738       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5739       gameInfo.holdingsSize = 7;
5740       break;
5741     case VariantJanus:
5742       pieces = JanusArray;
5743       gameInfo.boardWidth = 10;
5744       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5745       nrCastlingRights = 6;
5746         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5747         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5748         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5749         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5750         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5751         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5752       break;
5753     case VariantFalcon:
5754       pieces = FalconArray;
5755       gameInfo.boardWidth = 10;
5756       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5757       break;
5758     case VariantXiangqi:
5759       pieces = XiangqiArray;
5760       gameInfo.boardWidth  = 9;
5761       gameInfo.boardHeight = 10;
5762       nrCastlingRights = 0;
5763       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5764       break;
5765     case VariantShogi:
5766       pieces = ShogiArray;
5767       gameInfo.boardWidth  = 9;
5768       gameInfo.boardHeight = 9;
5769       gameInfo.holdingsSize = 7;
5770       nrCastlingRights = 0;
5771       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5772       break;
5773     case VariantCourier:
5774       pieces = CourierArray;
5775       gameInfo.boardWidth  = 12;
5776       nrCastlingRights = 0;
5777       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5778       break;
5779     case VariantKnightmate:
5780       pieces = KnightmateArray;
5781       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5782       break;
5783     case VariantSpartan:
5784       pieces = SpartanArray;
5785       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5786       break;
5787     case VariantFairy:
5788       pieces = fairyArray;
5789       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5790       break;
5791     case VariantGreat:
5792       pieces = GreatArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5795       gameInfo.holdingsSize = 8;
5796       break;
5797     case VariantSuper:
5798       pieces = FIDEArray;
5799       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5800       gameInfo.holdingsSize = 8;
5801       startedFromSetupPosition = TRUE;
5802       break;
5803     case VariantCrazyhouse:
5804     case VariantBughouse:
5805       pieces = FIDEArray;
5806       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5807       gameInfo.holdingsSize = 5;
5808       break;
5809     case VariantWildCastle:
5810       pieces = FIDEArray;
5811       /* !!?shuffle with kings guaranteed to be on d or e file */
5812       shuffleOpenings = 1;
5813       break;
5814     case VariantNoCastle:
5815       pieces = FIDEArray;
5816       nrCastlingRights = 0;
5817       /* !!?unconstrained back-rank shuffle */
5818       shuffleOpenings = 1;
5819       break;
5820     }
5821
5822     overrule = 0;
5823     if(appData.NrFiles >= 0) {
5824         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5825         gameInfo.boardWidth = appData.NrFiles;
5826     }
5827     if(appData.NrRanks >= 0) {
5828         gameInfo.boardHeight = appData.NrRanks;
5829     }
5830     if(appData.holdingsSize >= 0) {
5831         i = appData.holdingsSize;
5832         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5833         gameInfo.holdingsSize = i;
5834     }
5835     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5836     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5837         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5838
5839     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5840     if(pawnRow < 1) pawnRow = 1;
5841     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5842
5843     /* User pieceToChar list overrules defaults */
5844     if(appData.pieceToCharTable != NULL)
5845         SetCharTable(pieceToChar, appData.pieceToCharTable);
5846
5847     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5848
5849         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5850             s = (ChessSquare) 0; /* account holding counts in guard band */
5851         for( i=0; i<BOARD_HEIGHT; i++ )
5852             initialPosition[i][j] = s;
5853
5854         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5855         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5856         initialPosition[pawnRow][j] = WhitePawn;
5857         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5858         if(gameInfo.variant == VariantXiangqi) {
5859             if(j&1) {
5860                 initialPosition[pawnRow][j] =
5861                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5862                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5863                    initialPosition[2][j] = WhiteCannon;
5864                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5865                 }
5866             }
5867         }
5868         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5869     }
5870     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5871
5872             j=BOARD_LEFT+1;
5873             initialPosition[1][j] = WhiteBishop;
5874             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5875             j=BOARD_RGHT-2;
5876             initialPosition[1][j] = WhiteRook;
5877             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5878     }
5879
5880     if( nrCastlingRights == -1) {
5881         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5882         /*       This sets default castling rights from none to normal corners   */
5883         /* Variants with other castling rights must set them themselves above    */
5884         nrCastlingRights = 6;
5885
5886         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5887         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5888         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5889         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5890         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5891         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5892      }
5893
5894      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5895      if(gameInfo.variant == VariantGreat) { // promotion commoners
5896         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5897         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5898         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5899         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5900      }
5901      if( gameInfo.variant == VariantSChess ) {
5902       initialPosition[1][0] = BlackMarshall;
5903       initialPosition[2][0] = BlackAngel;
5904       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5905       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5906       initialPosition[1][1] = initialPosition[2][1] = 
5907       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5908      }
5909   if (appData.debugMode) {
5910     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5911   }
5912     if(shuffleOpenings) {
5913         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5914         startedFromSetupPosition = TRUE;
5915     }
5916     if(startedFromPositionFile) {
5917       /* [HGM] loadPos: use PositionFile for every new game */
5918       CopyBoard(initialPosition, filePosition);
5919       for(i=0; i<nrCastlingRights; i++)
5920           initialRights[i] = filePosition[CASTLING][i];
5921       startedFromSetupPosition = TRUE;
5922     }
5923
5924     CopyBoard(boards[0], initialPosition);
5925
5926     if(oldx != gameInfo.boardWidth ||
5927        oldy != gameInfo.boardHeight ||
5928        oldv != gameInfo.variant ||
5929        oldh != gameInfo.holdingsWidth
5930                                          )
5931             InitDrawingSizes(-2 ,0);
5932
5933     oldv = gameInfo.variant;
5934     if (redraw)
5935       DrawPosition(TRUE, boards[currentMove]);
5936 }
5937
5938 void
5939 SendBoard(cps, moveNum)
5940      ChessProgramState *cps;
5941      int moveNum;
5942 {
5943     char message[MSG_SIZ];
5944
5945     if (cps->useSetboard) {
5946       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5947       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5948       SendToProgram(message, cps);
5949       free(fen);
5950
5951     } else {
5952       ChessSquare *bp;
5953       int i, j;
5954       /* Kludge to set black to move, avoiding the troublesome and now
5955        * deprecated "black" command.
5956        */
5957       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5958         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5959
5960       SendToProgram("edit\n", cps);
5961       SendToProgram("#\n", cps);
5962       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5963         bp = &boards[moveNum][i][BOARD_LEFT];
5964         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5965           if ((int) *bp < (int) BlackPawn) {
5966             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5967                     AAA + j, ONE + i);
5968             if(message[0] == '+' || message[0] == '~') {
5969               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5970                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5971                         AAA + j, ONE + i);
5972             }
5973             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5974                 message[1] = BOARD_RGHT   - 1 - j + '1';
5975                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5976             }
5977             SendToProgram(message, cps);
5978           }
5979         }
5980       }
5981
5982       SendToProgram("c\n", cps);
5983       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5984         bp = &boards[moveNum][i][BOARD_LEFT];
5985         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5986           if (((int) *bp != (int) EmptySquare)
5987               && ((int) *bp >= (int) BlackPawn)) {
5988             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5989                     AAA + j, ONE + i);
5990             if(message[0] == '+' || message[0] == '~') {
5991               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5992                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5993                         AAA + j, ONE + i);
5994             }
5995             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5996                 message[1] = BOARD_RGHT   - 1 - j + '1';
5997                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5998             }
5999             SendToProgram(message, cps);
6000           }
6001         }
6002       }
6003
6004       SendToProgram(".\n", cps);
6005     }
6006     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6007 }
6008
6009 ChessSquare
6010 DefaultPromoChoice(int white)
6011 {
6012     ChessSquare result;
6013     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6014         result = WhiteFerz; // no choice
6015     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6016         result= WhiteKing; // in Suicide Q is the last thing we want
6017     else if(gameInfo.variant == VariantSpartan)
6018         result = white ? WhiteQueen : WhiteAngel;
6019     else result = WhiteQueen;
6020     if(!white) result = WHITE_TO_BLACK result;
6021     return result;
6022 }
6023
6024 static int autoQueen; // [HGM] oneclick
6025
6026 int
6027 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6028 {
6029     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6030     /* [HGM] add Shogi promotions */
6031     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6032     ChessSquare piece;
6033     ChessMove moveType;
6034     Boolean premove;
6035
6036     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6037     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6038
6039     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6040       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6041         return FALSE;
6042
6043     piece = boards[currentMove][fromY][fromX];
6044     if(gameInfo.variant == VariantShogi) {
6045         promotionZoneSize = BOARD_HEIGHT/3;
6046         highestPromotingPiece = (int)WhiteFerz;
6047     } else if(gameInfo.variant == VariantMakruk) {
6048         promotionZoneSize = 3;
6049     }
6050
6051     // Treat Lance as Pawn when it is not representing Amazon
6052     if(gameInfo.variant != VariantSuper) {
6053         if(piece == WhiteLance) piece = WhitePawn; else
6054         if(piece == BlackLance) piece = BlackPawn;
6055     }
6056
6057     // next weed out all moves that do not touch the promotion zone at all
6058     if((int)piece >= BlackPawn) {
6059         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6060              return FALSE;
6061         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6062     } else {
6063         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6064            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6065     }
6066
6067     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6068
6069     // weed out mandatory Shogi promotions
6070     if(gameInfo.variant == VariantShogi) {
6071         if(piece >= BlackPawn) {
6072             if(toY == 0 && piece == BlackPawn ||
6073                toY == 0 && piece == BlackQueen ||
6074                toY <= 1 && piece == BlackKnight) {
6075                 *promoChoice = '+';
6076                 return FALSE;
6077             }
6078         } else {
6079             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6080                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6081                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6082                 *promoChoice = '+';
6083                 return FALSE;
6084             }
6085         }
6086     }
6087
6088     // weed out obviously illegal Pawn moves
6089     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6090         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6091         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6092         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6093         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6094         // note we are not allowed to test for valid (non-)capture, due to premove
6095     }
6096
6097     // we either have a choice what to promote to, or (in Shogi) whether to promote
6098     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6099         *promoChoice = PieceToChar(BlackFerz);  // no choice
6100         return FALSE;
6101     }
6102     // no sense asking what we must promote to if it is going to explode...
6103     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6104         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6105         return FALSE;
6106     }
6107     // give caller the default choice even if we will not make it
6108     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6109     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6110     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6111                            && gameInfo.variant != VariantShogi
6112                            && gameInfo.variant != VariantSuper) return FALSE;
6113     if(autoQueen) return FALSE; // predetermined
6114
6115     // suppress promotion popup on illegal moves that are not premoves
6116     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6117               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6118     if(appData.testLegality && !premove) {
6119         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6120                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6121         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6122             return FALSE;
6123     }
6124
6125     return TRUE;
6126 }
6127
6128 int
6129 InPalace(row, column)
6130      int row, column;
6131 {   /* [HGM] for Xiangqi */
6132     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6133          column < (BOARD_WIDTH + 4)/2 &&
6134          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6135     return FALSE;
6136 }
6137
6138 int
6139 PieceForSquare (x, y)
6140      int x;
6141      int y;
6142 {
6143   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6144      return -1;
6145   else
6146      return boards[currentMove][y][x];
6147 }
6148
6149 int
6150 OKToStartUserMove(x, y)
6151      int x, y;
6152 {
6153     ChessSquare from_piece;
6154     int white_piece;
6155
6156     if (matchMode) return FALSE;
6157     if (gameMode == EditPosition) return TRUE;
6158
6159     if (x >= 0 && y >= 0)
6160       from_piece = boards[currentMove][y][x];
6161     else
6162       from_piece = EmptySquare;
6163
6164     if (from_piece == EmptySquare) return FALSE;
6165
6166     white_piece = (int)from_piece >= (int)WhitePawn &&
6167       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6168
6169     switch (gameMode) {
6170       case PlayFromGameFile:
6171       case AnalyzeFile:
6172       case TwoMachinesPlay:
6173       case EndOfGame:
6174         return FALSE;
6175
6176       case IcsObserving:
6177       case IcsIdle:
6178         return FALSE;
6179
6180       case MachinePlaysWhite:
6181       case IcsPlayingBlack:
6182         if (appData.zippyPlay) return FALSE;
6183         if (white_piece) {
6184             DisplayMoveError(_("You are playing Black"));
6185             return FALSE;
6186         }
6187         break;
6188
6189       case MachinePlaysBlack:
6190       case IcsPlayingWhite:
6191         if (appData.zippyPlay) return FALSE;
6192         if (!white_piece) {
6193             DisplayMoveError(_("You are playing White"));
6194             return FALSE;
6195         }
6196         break;
6197
6198       case EditGame:
6199         if (!white_piece && WhiteOnMove(currentMove)) {
6200             DisplayMoveError(_("It is White's turn"));
6201             return FALSE;
6202         }
6203         if (white_piece && !WhiteOnMove(currentMove)) {
6204             DisplayMoveError(_("It is Black's turn"));
6205             return FALSE;
6206         }
6207         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6208             /* Editing correspondence game history */
6209             /* Could disallow this or prompt for confirmation */
6210             cmailOldMove = -1;
6211         }
6212         break;
6213
6214       case BeginningOfGame:
6215         if (appData.icsActive) return FALSE;
6216         if (!appData.noChessProgram) {
6217             if (!white_piece) {
6218                 DisplayMoveError(_("You are playing White"));
6219                 return FALSE;
6220             }
6221         }
6222         break;
6223
6224       case Training:
6225         if (!white_piece && WhiteOnMove(currentMove)) {
6226             DisplayMoveError(_("It is White's turn"));
6227             return FALSE;
6228         }
6229         if (white_piece && !WhiteOnMove(currentMove)) {
6230             DisplayMoveError(_("It is Black's turn"));
6231             return FALSE;
6232         }
6233         break;
6234
6235       default:
6236       case IcsExamining:
6237         break;
6238     }
6239     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6240         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6241         && gameMode != AnalyzeFile && gameMode != Training) {
6242         DisplayMoveError(_("Displayed position is not current"));
6243         return FALSE;
6244     }
6245     return TRUE;
6246 }
6247
6248 Boolean
6249 OnlyMove(int *x, int *y, Boolean captures) {
6250     DisambiguateClosure cl;
6251     if (appData.zippyPlay) return FALSE;
6252     switch(gameMode) {
6253       case MachinePlaysBlack:
6254       case IcsPlayingWhite:
6255       case BeginningOfGame:
6256         if(!WhiteOnMove(currentMove)) return FALSE;
6257         break;
6258       case MachinePlaysWhite:
6259       case IcsPlayingBlack:
6260         if(WhiteOnMove(currentMove)) return FALSE;
6261         break;
6262       case EditGame:
6263         break;
6264       default:
6265         return FALSE;
6266     }
6267     cl.pieceIn = EmptySquare;
6268     cl.rfIn = *y;
6269     cl.ffIn = *x;
6270     cl.rtIn = -1;
6271     cl.ftIn = -1;
6272     cl.promoCharIn = NULLCHAR;
6273     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6274     if( cl.kind == NormalMove ||
6275         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6276         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6277         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6278       fromX = cl.ff;
6279       fromY = cl.rf;
6280       *x = cl.ft;
6281       *y = cl.rt;
6282       return TRUE;
6283     }
6284     if(cl.kind != ImpossibleMove) return FALSE;
6285     cl.pieceIn = EmptySquare;
6286     cl.rfIn = -1;
6287     cl.ffIn = -1;
6288     cl.rtIn = *y;
6289     cl.ftIn = *x;
6290     cl.promoCharIn = NULLCHAR;
6291     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6292     if( cl.kind == NormalMove ||
6293         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6294         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6295         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6296       fromX = cl.ff;
6297       fromY = cl.rf;
6298       *x = cl.ft;
6299       *y = cl.rt;
6300       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6301       return TRUE;
6302     }
6303     return FALSE;
6304 }
6305
6306 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6307 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6308 int lastLoadGameUseList = FALSE;
6309 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6310 ChessMove lastLoadGameStart = EndOfFile;
6311
6312 void
6313 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6314      int fromX, fromY, toX, toY;
6315      int promoChar;
6316 {
6317     ChessMove moveType;
6318     ChessSquare pdown, pup;
6319
6320     /* Check if the user is playing in turn.  This is complicated because we
6321        let the user "pick up" a piece before it is his turn.  So the piece he
6322        tried to pick up may have been captured by the time he puts it down!
6323        Therefore we use the color the user is supposed to be playing in this
6324        test, not the color of the piece that is currently on the starting
6325        square---except in EditGame mode, where the user is playing both
6326        sides; fortunately there the capture race can't happen.  (It can
6327        now happen in IcsExamining mode, but that's just too bad.  The user
6328        will get a somewhat confusing message in that case.)
6329        */
6330
6331     switch (gameMode) {
6332       case PlayFromGameFile:
6333       case AnalyzeFile:
6334       case TwoMachinesPlay:
6335       case EndOfGame:
6336       case IcsObserving:
6337       case IcsIdle:
6338         /* We switched into a game mode where moves are not accepted,
6339            perhaps while the mouse button was down. */
6340         return;
6341
6342       case MachinePlaysWhite:
6343         /* User is moving for Black */
6344         if (WhiteOnMove(currentMove)) {
6345             DisplayMoveError(_("It is White's turn"));
6346             return;
6347         }
6348         break;
6349
6350       case MachinePlaysBlack:
6351         /* User is moving for White */
6352         if (!WhiteOnMove(currentMove)) {
6353             DisplayMoveError(_("It is Black's turn"));
6354             return;
6355         }
6356         break;
6357
6358       case EditGame:
6359       case IcsExamining:
6360       case BeginningOfGame:
6361       case AnalyzeMode:
6362       case Training:
6363         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6364         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6365             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6366             /* User is moving for Black */
6367             if (WhiteOnMove(currentMove)) {
6368                 DisplayMoveError(_("It is White's turn"));
6369                 return;
6370             }
6371         } else {
6372             /* User is moving for White */
6373             if (!WhiteOnMove(currentMove)) {
6374                 DisplayMoveError(_("It is Black's turn"));
6375                 return;
6376             }
6377         }
6378         break;
6379
6380       case IcsPlayingBlack:
6381         /* User is moving for Black */
6382         if (WhiteOnMove(currentMove)) {
6383             if (!appData.premove) {
6384                 DisplayMoveError(_("It is White's turn"));
6385             } else if (toX >= 0 && toY >= 0) {
6386                 premoveToX = toX;
6387                 premoveToY = toY;
6388                 premoveFromX = fromX;
6389                 premoveFromY = fromY;
6390                 premovePromoChar = promoChar;
6391                 gotPremove = 1;
6392                 if (appData.debugMode)
6393                     fprintf(debugFP, "Got premove: fromX %d,"
6394                             "fromY %d, toX %d, toY %d\n",
6395                             fromX, fromY, toX, toY);
6396             }
6397             return;
6398         }
6399         break;
6400
6401       case IcsPlayingWhite:
6402         /* User is moving for White */
6403         if (!WhiteOnMove(currentMove)) {
6404             if (!appData.premove) {
6405                 DisplayMoveError(_("It is Black's turn"));
6406             } else if (toX >= 0 && toY >= 0) {
6407                 premoveToX = toX;
6408                 premoveToY = toY;
6409                 premoveFromX = fromX;
6410                 premoveFromY = fromY;
6411                 premovePromoChar = promoChar;
6412                 gotPremove = 1;
6413                 if (appData.debugMode)
6414                     fprintf(debugFP, "Got premove: fromX %d,"
6415                             "fromY %d, toX %d, toY %d\n",
6416                             fromX, fromY, toX, toY);
6417             }
6418             return;
6419         }
6420         break;
6421
6422       default:
6423         break;
6424
6425       case EditPosition:
6426         /* EditPosition, empty square, or different color piece;
6427            click-click move is possible */
6428         if (toX == -2 || toY == -2) {
6429             boards[0][fromY][fromX] = EmptySquare;
6430             DrawPosition(FALSE, boards[currentMove]);
6431             return;
6432         } else if (toX >= 0 && toY >= 0) {
6433             boards[0][toY][toX] = boards[0][fromY][fromX];
6434             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6435                 if(boards[0][fromY][0] != EmptySquare) {
6436                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6437                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6438                 }
6439             } else
6440             if(fromX == BOARD_RGHT+1) {
6441                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6442                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6443                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6444                 }
6445             } else
6446             boards[0][fromY][fromX] = EmptySquare;
6447             DrawPosition(FALSE, boards[currentMove]);
6448             return;
6449         }
6450         return;
6451     }
6452
6453     if(toX < 0 || toY < 0) return;
6454     pdown = boards[currentMove][fromY][fromX];
6455     pup = boards[currentMove][toY][toX];
6456
6457     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6458     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6459          if( pup != EmptySquare ) return;
6460          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6461            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6462                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6463            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6464            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6465            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6466            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6467          fromY = DROP_RANK;
6468     }
6469
6470     /* [HGM] always test for legality, to get promotion info */
6471     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6472                                          fromY, fromX, toY, toX, promoChar);
6473     /* [HGM] but possibly ignore an IllegalMove result */
6474     if (appData.testLegality) {
6475         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6476             DisplayMoveError(_("Illegal move"));
6477             return;
6478         }
6479     }
6480
6481     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6482 }
6483
6484 /* Common tail of UserMoveEvent and DropMenuEvent */
6485 int
6486 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6487      ChessMove moveType;
6488      int fromX, fromY, toX, toY;
6489      /*char*/int promoChar;
6490 {
6491     char *bookHit = 0;
6492
6493     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6494         // [HGM] superchess: suppress promotions to non-available piece
6495         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6496         if(WhiteOnMove(currentMove)) {
6497             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6498         } else {
6499             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6500         }
6501     }
6502
6503     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6504        move type in caller when we know the move is a legal promotion */
6505     if(moveType == NormalMove && promoChar)
6506         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6507
6508     /* [HGM] <popupFix> The following if has been moved here from
6509        UserMoveEvent(). Because it seemed to belong here (why not allow
6510        piece drops in training games?), and because it can only be
6511        performed after it is known to what we promote. */
6512     if (gameMode == Training) {
6513       /* compare the move played on the board to the next move in the
6514        * game. If they match, display the move and the opponent's response.
6515        * If they don't match, display an error message.
6516        */
6517       int saveAnimate;
6518       Board testBoard;
6519       CopyBoard(testBoard, boards[currentMove]);
6520       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6521
6522       if (CompareBoards(testBoard, boards[currentMove+1])) {
6523         ForwardInner(currentMove+1);
6524
6525         /* Autoplay the opponent's response.
6526          * if appData.animate was TRUE when Training mode was entered,
6527          * the response will be animated.
6528          */
6529         saveAnimate = appData.animate;
6530         appData.animate = animateTraining;
6531         ForwardInner(currentMove+1);
6532         appData.animate = saveAnimate;
6533
6534         /* check for the end of the game */
6535         if (currentMove >= forwardMostMove) {
6536           gameMode = PlayFromGameFile;
6537           ModeHighlight();
6538           SetTrainingModeOff();
6539           DisplayInformation(_("End of game"));
6540         }
6541       } else {
6542         DisplayError(_("Incorrect move"), 0);
6543       }
6544       return 1;
6545     }
6546
6547   /* Ok, now we know that the move is good, so we can kill
6548      the previous line in Analysis Mode */
6549   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6550                                 && currentMove < forwardMostMove) {
6551     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6552     else forwardMostMove = currentMove;
6553   }
6554
6555   /* If we need the chess program but it's dead, restart it */
6556   ResurrectChessProgram();
6557
6558   /* A user move restarts a paused game*/
6559   if (pausing)
6560     PauseEvent();
6561
6562   thinkOutput[0] = NULLCHAR;
6563
6564   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6565
6566   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6567     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6568     return 1;
6569   }
6570
6571   if (gameMode == BeginningOfGame) {
6572     if (appData.noChessProgram) {
6573       gameMode = EditGame;
6574       SetGameInfo();
6575     } else {
6576       char buf[MSG_SIZ];
6577       gameMode = MachinePlaysBlack;
6578       StartClocks();
6579       SetGameInfo();
6580       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6581       DisplayTitle(buf);
6582       if (first.sendName) {
6583         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6584         SendToProgram(buf, &first);
6585       }
6586       StartClocks();
6587     }
6588     ModeHighlight();
6589   }
6590
6591   /* Relay move to ICS or chess engine */
6592   if (appData.icsActive) {
6593     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6594         gameMode == IcsExamining) {
6595       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6596         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6597         SendToICS("draw ");
6598         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6599       }
6600       // also send plain move, in case ICS does not understand atomic claims
6601       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6602       ics_user_moved = 1;
6603     }
6604   } else {
6605     if (first.sendTime && (gameMode == BeginningOfGame ||
6606                            gameMode == MachinePlaysWhite ||
6607                            gameMode == MachinePlaysBlack)) {
6608       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6609     }
6610     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6611          // [HGM] book: if program might be playing, let it use book
6612         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6613         first.maybeThinking = TRUE;
6614     } else SendMoveToProgram(forwardMostMove-1, &first);
6615     if (currentMove == cmailOldMove + 1) {
6616       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6617     }
6618   }
6619
6620   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6621
6622   switch (gameMode) {
6623   case EditGame:
6624     if(appData.testLegality)
6625     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6626     case MT_NONE:
6627     case MT_CHECK:
6628       break;
6629     case MT_CHECKMATE:
6630     case MT_STAINMATE:
6631       if (WhiteOnMove(currentMove)) {
6632         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6633       } else {
6634         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6635       }
6636       break;
6637     case MT_STALEMATE:
6638       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6639       break;
6640     }
6641     break;
6642
6643   case MachinePlaysBlack:
6644   case MachinePlaysWhite:
6645     /* disable certain menu options while machine is thinking */
6646     SetMachineThinkingEnables();
6647     break;
6648
6649   default:
6650     break;
6651   }
6652
6653   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6654   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6655
6656   if(bookHit) { // [HGM] book: simulate book reply
6657         static char bookMove[MSG_SIZ]; // a bit generous?
6658
6659         programStats.nodes = programStats.depth = programStats.time =
6660         programStats.score = programStats.got_only_move = 0;
6661         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6662
6663         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6664         strcat(bookMove, bookHit);
6665         HandleMachineMove(bookMove, &first);
6666   }
6667   return 1;
6668 }
6669
6670 void
6671 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6672      Board board;
6673      int flags;
6674      ChessMove kind;
6675      int rf, ff, rt, ft;
6676      VOIDSTAR closure;
6677 {
6678     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6679     Markers *m = (Markers *) closure;
6680     if(rf == fromY && ff == fromX)
6681         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6682                          || kind == WhiteCapturesEnPassant
6683                          || kind == BlackCapturesEnPassant);
6684     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6685 }
6686
6687 void
6688 MarkTargetSquares(int clear)
6689 {
6690   int x, y;
6691   if(!appData.markers || !appData.highlightDragging ||
6692      !appData.testLegality || gameMode == EditPosition) return;
6693   if(clear) {
6694     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6695   } else {
6696     int capt = 0;
6697     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6698     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6699       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6700       if(capt)
6701       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6702     }
6703   }
6704   DrawPosition(TRUE, NULL);
6705 }
6706
6707 int
6708 Explode(Board board, int fromX, int fromY, int toX, int toY)
6709 {
6710     if(gameInfo.variant == VariantAtomic &&
6711        (board[toY][toX] != EmptySquare ||                     // capture?
6712         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6713                          board[fromY][fromX] == BlackPawn   )
6714       )) {
6715         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6716         return TRUE;
6717     }
6718     return FALSE;
6719 }
6720
6721 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6722
6723 int CanPromote(ChessSquare piece, int y)
6724 {
6725         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6726         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6727         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6728            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6729            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6730                                                   gameInfo.variant == VariantMakruk) return FALSE;
6731         return (piece == BlackPawn && y == 1 ||
6732                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6733                 piece == BlackLance && y == 1 ||
6734                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6735 }
6736
6737 void LeftClick(ClickType clickType, int xPix, int yPix)
6738 {
6739     int x, y;
6740     Boolean saveAnimate;
6741     static int second = 0, promotionChoice = 0, clearFlag = 0;
6742     char promoChoice = NULLCHAR;
6743     ChessSquare piece;
6744
6745     if(appData.seekGraph && appData.icsActive && loggedOn &&
6746         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6747         SeekGraphClick(clickType, xPix, yPix, 0);
6748         return;
6749     }
6750
6751     if (clickType == Press) ErrorPopDown();
6752     MarkTargetSquares(1);
6753
6754     x = EventToSquare(xPix, BOARD_WIDTH);
6755     y = EventToSquare(yPix, BOARD_HEIGHT);
6756     if (!flipView && y >= 0) {
6757         y = BOARD_HEIGHT - 1 - y;
6758     }
6759     if (flipView && x >= 0) {
6760         x = BOARD_WIDTH - 1 - x;
6761     }
6762
6763     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6764         defaultPromoChoice = promoSweep;
6765         promoSweep = EmptySquare;   // terminate sweep
6766         promoDefaultAltered = TRUE;
6767         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6768     }
6769
6770     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6771         if(clickType == Release) return; // ignore upclick of click-click destination
6772         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6773         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6774         if(gameInfo.holdingsWidth &&
6775                 (WhiteOnMove(currentMove)
6776                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6777                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6778             // click in right holdings, for determining promotion piece
6779             ChessSquare p = boards[currentMove][y][x];
6780             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6781             if(p != EmptySquare) {
6782                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6783                 fromX = fromY = -1;
6784                 return;
6785             }
6786         }
6787         DrawPosition(FALSE, boards[currentMove]);
6788         return;
6789     }
6790
6791     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6792     if(clickType == Press
6793             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6794               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6795               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6796         return;
6797
6798     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6799         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6800
6801     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6802         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6803                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6804         defaultPromoChoice = DefaultPromoChoice(side);
6805     }
6806
6807     autoQueen = appData.alwaysPromoteToQueen;
6808
6809     if (fromX == -1) {
6810       int originalY = y;
6811       gatingPiece = EmptySquare;
6812       if (clickType != Press) {
6813         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6814             DragPieceEnd(xPix, yPix); dragging = 0;
6815             DrawPosition(FALSE, NULL);
6816         }
6817         return;
6818       }
6819       fromX = x; fromY = y;
6820       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6821          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6822          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6823             /* First square */
6824             if (OKToStartUserMove(fromX, fromY)) {
6825                 second = 0;
6826                 MarkTargetSquares(0);
6827                 DragPieceBegin(xPix, yPix); dragging = 1;
6828                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6829                     promoSweep = defaultPromoChoice;
6830                     selectFlag = 0; lastX = xPix; lastY = yPix;
6831                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6832                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6833                 }
6834                 if (appData.highlightDragging) {
6835                     SetHighlights(fromX, fromY, -1, -1);
6836                 }
6837             } else fromX = fromY = -1;
6838             return;
6839         }
6840     }
6841
6842     /* fromX != -1 */
6843     if (clickType == Press && gameMode != EditPosition) {
6844         ChessSquare fromP;
6845         ChessSquare toP;
6846         int frc;
6847
6848         // ignore off-board to clicks
6849         if(y < 0 || x < 0) return;
6850
6851         /* Check if clicking again on the same color piece */
6852         fromP = boards[currentMove][fromY][fromX];
6853         toP = boards[currentMove][y][x];
6854         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6855         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6856              WhitePawn <= toP && toP <= WhiteKing &&
6857              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6858              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6859             (BlackPawn <= fromP && fromP <= BlackKing &&
6860              BlackPawn <= toP && toP <= BlackKing &&
6861              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6862              !(fromP == BlackKing && toP == BlackRook && frc))) {
6863             /* Clicked again on same color piece -- changed his mind */
6864             second = (x == fromX && y == fromY);
6865             promoDefaultAltered = FALSE;
6866            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6867             if (appData.highlightDragging) {
6868                 SetHighlights(x, y, -1, -1);
6869             } else {
6870                 ClearHighlights();
6871             }
6872             if (OKToStartUserMove(x, y)) {
6873                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6874                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6875                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6876                  gatingPiece = boards[currentMove][fromY][fromX];
6877                 else gatingPiece = EmptySquare;
6878                 fromX = x;
6879                 fromY = y; dragging = 1;
6880                 MarkTargetSquares(0);
6881                 DragPieceBegin(xPix, yPix);
6882                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6883                     promoSweep = defaultPromoChoice;
6884                     selectFlag = 0; lastX = xPix; lastY = yPix;
6885                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6886                 }
6887             }
6888            }
6889            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6890            second = FALSE; 
6891         }
6892         // ignore clicks on holdings
6893         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6894     }
6895
6896     if (clickType == Release && x == fromX && y == fromY) {
6897         DragPieceEnd(xPix, yPix); dragging = 0;
6898         if(clearFlag) {
6899             // a deferred attempt to click-click move an empty square on top of a piece
6900             boards[currentMove][y][x] = EmptySquare;
6901             ClearHighlights();
6902             DrawPosition(FALSE, boards[currentMove]);
6903             fromX = fromY = -1; clearFlag = 0;
6904             return;
6905         }
6906         if (appData.animateDragging) {
6907             /* Undo animation damage if any */
6908             DrawPosition(FALSE, NULL);
6909         }
6910         if (second) {
6911             /* Second up/down in same square; just abort move */
6912             second = 0;
6913             fromX = fromY = -1;
6914             gatingPiece = EmptySquare;
6915             ClearHighlights();
6916             gotPremove = 0;
6917             ClearPremoveHighlights();
6918         } else {
6919             /* First upclick in same square; start click-click mode */
6920             SetHighlights(x, y, -1, -1);
6921         }
6922         return;
6923     }
6924
6925     clearFlag = 0;
6926
6927     /* we now have a different from- and (possibly off-board) to-square */
6928     /* Completed move */
6929     toX = x;
6930     toY = y;
6931     saveAnimate = appData.animate;
6932     if (clickType == Press) {
6933         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6934             // must be Edit Position mode with empty-square selected
6935             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6936             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6937             return;
6938         }
6939         /* Finish clickclick move */
6940         if (appData.animate || appData.highlightLastMove) {
6941             SetHighlights(fromX, fromY, toX, toY);
6942         } else {
6943             ClearHighlights();
6944         }
6945     } else {
6946         /* Finish drag move */
6947         if (appData.highlightLastMove) {
6948             SetHighlights(fromX, fromY, toX, toY);
6949         } else {
6950             ClearHighlights();
6951         }
6952         DragPieceEnd(xPix, yPix); dragging = 0;
6953         /* Don't animate move and drag both */
6954         appData.animate = FALSE;
6955     }
6956
6957     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6958     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6959         ChessSquare piece = boards[currentMove][fromY][fromX];
6960         if(gameMode == EditPosition && piece != EmptySquare &&
6961            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6962             int n;
6963
6964             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6965                 n = PieceToNumber(piece - (int)BlackPawn);
6966                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6967                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6968                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6969             } else
6970             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6971                 n = PieceToNumber(piece);
6972                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6973                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6974                 boards[currentMove][n][BOARD_WIDTH-2]++;
6975             }
6976             boards[currentMove][fromY][fromX] = EmptySquare;
6977         }
6978         ClearHighlights();
6979         fromX = fromY = -1;
6980         DrawPosition(TRUE, boards[currentMove]);
6981         return;
6982     }
6983
6984     // off-board moves should not be highlighted
6985     if(x < 0 || y < 0) ClearHighlights();
6986
6987     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6988
6989     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6990         SetHighlights(fromX, fromY, toX, toY);
6991         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6992             // [HGM] super: promotion to captured piece selected from holdings
6993             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6994             promotionChoice = TRUE;
6995             // kludge follows to temporarily execute move on display, without promoting yet
6996             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6997             boards[currentMove][toY][toX] = p;
6998             DrawPosition(FALSE, boards[currentMove]);
6999             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7000             boards[currentMove][toY][toX] = q;
7001             DisplayMessage("Click in holdings to choose piece", "");
7002             return;
7003         }
7004         PromotionPopUp();
7005     } else {
7006         int oldMove = currentMove;
7007         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7008         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7009         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7010         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7011            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7012             DrawPosition(TRUE, boards[currentMove]);
7013         fromX = fromY = -1;
7014     }
7015     appData.animate = saveAnimate;
7016     if (appData.animate || appData.animateDragging) {
7017         /* Undo animation damage if needed */
7018         DrawPosition(FALSE, NULL);
7019     }
7020 }
7021
7022 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7023 {   // front-end-free part taken out of PieceMenuPopup
7024     int whichMenu; int xSqr, ySqr;
7025
7026     if(seekGraphUp) { // [HGM] seekgraph
7027         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7028         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7029         return -2;
7030     }
7031
7032     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7033          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7034         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7035         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7036         if(action == Press)   {
7037             originalFlip = flipView;
7038             flipView = !flipView; // temporarily flip board to see game from partners perspective
7039             DrawPosition(TRUE, partnerBoard);
7040             DisplayMessage(partnerStatus, "");
7041             partnerUp = TRUE;
7042         } else if(action == Release) {
7043             flipView = originalFlip;
7044             DrawPosition(TRUE, boards[currentMove]);
7045             partnerUp = FALSE;
7046         }
7047         return -2;
7048     }
7049
7050     xSqr = EventToSquare(x, BOARD_WIDTH);
7051     ySqr = EventToSquare(y, BOARD_HEIGHT);
7052     if (action == Release) {
7053         if(pieceSweep != EmptySquare) {
7054             EditPositionMenuEvent(pieceSweep, toX, toY);
7055             pieceSweep = EmptySquare;
7056         } else UnLoadPV(); // [HGM] pv
7057     }
7058     if (action != Press) return -2; // return code to be ignored
7059     switch (gameMode) {
7060       case IcsExamining:
7061         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7062       case EditPosition:
7063         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7064         if (xSqr < 0 || ySqr < 0) return -1;
7065         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7066         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7067         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7068         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7069         NextPiece(0);
7070         return -2;\r
7071       case IcsObserving:
7072         if(!appData.icsEngineAnalyze) return -1;
7073       case IcsPlayingWhite:
7074       case IcsPlayingBlack:
7075         if(!appData.zippyPlay) goto noZip;
7076       case AnalyzeMode:
7077       case AnalyzeFile:
7078       case MachinePlaysWhite:
7079       case MachinePlaysBlack:
7080       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7081         if (!appData.dropMenu) {
7082           LoadPV(x, y);
7083           return 2; // flag front-end to grab mouse events
7084         }
7085         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7086            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7087       case EditGame:
7088       noZip:
7089         if (xSqr < 0 || ySqr < 0) return -1;
7090         if (!appData.dropMenu || appData.testLegality &&
7091             gameInfo.variant != VariantBughouse &&
7092             gameInfo.variant != VariantCrazyhouse) return -1;
7093         whichMenu = 1; // drop menu
7094         break;
7095       default:
7096         return -1;
7097     }
7098
7099     if (((*fromX = xSqr) < 0) ||
7100         ((*fromY = ySqr) < 0)) {
7101         *fromX = *fromY = -1;
7102         return -1;
7103     }
7104     if (flipView)
7105       *fromX = BOARD_WIDTH - 1 - *fromX;
7106     else
7107       *fromY = BOARD_HEIGHT - 1 - *fromY;
7108
7109     return whichMenu;
7110 }
7111
7112 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7113 {
7114 //    char * hint = lastHint;
7115     FrontEndProgramStats stats;
7116
7117     stats.which = cps == &first ? 0 : 1;
7118     stats.depth = cpstats->depth;
7119     stats.nodes = cpstats->nodes;
7120     stats.score = cpstats->score;
7121     stats.time = cpstats->time;
7122     stats.pv = cpstats->movelist;
7123     stats.hint = lastHint;
7124     stats.an_move_index = 0;
7125     stats.an_move_count = 0;
7126
7127     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7128         stats.hint = cpstats->move_name;
7129         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7130         stats.an_move_count = cpstats->nr_moves;
7131     }
7132
7133     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
7134
7135     SetProgramStats( &stats );
7136 }
7137
7138 void
7139 ClearEngineOutputPane(int which)
7140 {
7141     static FrontEndProgramStats dummyStats;
7142     dummyStats.which = which;
7143     dummyStats.pv = "#";
7144     SetProgramStats( &dummyStats );
7145 }
7146
7147 #define MAXPLAYERS 500
7148
7149 char *
7150 TourneyStandings(int display)
7151 {
7152     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7153     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7154     char result, *p, *names[MAXPLAYERS];
7155
7156     names[0] = p = strdup(appData.participants);
7157     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7158
7159     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7160
7161     while(result = appData.results[nr]) {
7162         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7163         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7164         wScore = bScore = 0;
7165         switch(result) {
7166           case '+': wScore = 2; break;
7167           case '-': bScore = 2; break;
7168           case '=': wScore = bScore = 1; break;
7169           case ' ':
7170           case '*': return strdup("busy"); // tourney not finished
7171         }
7172         score[w] += wScore;
7173         score[b] += bScore;
7174         games[w]++;
7175         games[b]++;
7176         nr++;
7177     }
7178     if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7179     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7180     for(w=0; w<nPlayers; w++) {
7181         bScore = -1;
7182         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7183         ranking[w] = b; points[w] = bScore; score[b] = -2;
7184     }
7185     p = malloc(nPlayers*34+1);
7186     for(w=0; w<nPlayers && w<display; w++)
7187         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7188     free(names[0]);
7189     return p;
7190 }
7191
7192 void
7193 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7194 {       // count all piece types
7195         int p, f, r;
7196         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7197         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7198         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7199                 p = board[r][f];
7200                 pCnt[p]++;
7201                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7202                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7203                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7204                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7205                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7206                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7207         }
7208 }
7209
7210 int
7211 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7212 {
7213         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7214         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7215
7216         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7217         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7218         if(myPawns == 2 && nMine == 3) // KPP
7219             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7220         if(myPawns == 1 && nMine == 2) // KP
7221             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7222         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7223             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7224         if(myPawns) return FALSE;
7225         if(pCnt[WhiteRook+side])
7226             return pCnt[BlackRook-side] ||
7227                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7228                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7229                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7230         if(pCnt[WhiteCannon+side]) {
7231             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7232             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7233         }
7234         if(pCnt[WhiteKnight+side])
7235             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7236         return FALSE;
7237 }
7238
7239 int
7240 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7241 {
7242         VariantClass v = gameInfo.variant;
7243
7244         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7245         if(v == VariantShatranj) return TRUE; // always winnable through baring
7246         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7247         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7248
7249         if(v == VariantXiangqi) {
7250                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7251
7252                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7253                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7254                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7255                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7256                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7257                 if(stale) // we have at least one last-rank P plus perhaps C
7258                     return majors // KPKX
7259                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7260                 else // KCA*E*
7261                     return pCnt[WhiteFerz+side] // KCAK
7262                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7263                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7264                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7265
7266         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7267                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7268
7269                 if(nMine == 1) return FALSE; // bare King
7270                 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
7271                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7272                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7273                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7274                 if(pCnt[WhiteKnight+side])
7275                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7276                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7277                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7278                 if(nBishops)
7279                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7280                 if(pCnt[WhiteAlfil+side])
7281                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7282                 if(pCnt[WhiteWazir+side])
7283                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7284         }
7285
7286         return TRUE;
7287 }
7288
7289 int
7290 Adjudicate(ChessProgramState *cps)
7291 {       // [HGM] some adjudications useful with buggy engines
7292         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7293         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7294         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7295         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7296         int k, count = 0; static int bare = 1;
7297         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7298         Boolean canAdjudicate = !appData.icsActive;
7299
7300         // most tests only when we understand the game, i.e. legality-checking on
7301             if( appData.testLegality )
7302             {   /* [HGM] Some more adjudications for obstinate engines */
7303                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7304                 static int moveCount = 6;
7305                 ChessMove result;
7306                 char *reason = NULL;
7307
7308                 /* Count what is on board. */
7309                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7310
7311                 /* Some material-based adjudications that have to be made before stalemate test */
7312                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7313                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7314                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7315                      if(canAdjudicate && appData.checkMates) {
7316                          if(engineOpponent)
7317                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7318                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7319                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7320                          return 1;
7321                      }
7322                 }
7323
7324                 /* Bare King in Shatranj (loses) or Losers (wins) */
7325                 if( nrW == 1 || nrB == 1) {
7326                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7327                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7328                      if(canAdjudicate && appData.checkMates) {
7329                          if(engineOpponent)
7330                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7331                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7332                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7333                          return 1;
7334                      }
7335                   } else
7336                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7337                   {    /* bare King */
7338                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7339                         if(canAdjudicate && appData.checkMates) {
7340                             /* but only adjudicate if adjudication enabled */
7341                             if(engineOpponent)
7342                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7343                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7344                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7345                             return 1;
7346                         }
7347                   }
7348                 } else bare = 1;
7349
7350
7351             // don't wait for engine to announce game end if we can judge ourselves
7352             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7353               case MT_CHECK:
7354                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7355                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7356                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7357                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7358                             checkCnt++;
7359                         if(checkCnt >= 2) {
7360                             reason = "Xboard adjudication: 3rd check";
7361                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7362                             break;
7363                         }
7364                     }
7365                 }
7366               case MT_NONE:
7367               default:
7368                 break;
7369               case MT_STALEMATE:
7370               case MT_STAINMATE:
7371                 reason = "Xboard adjudication: Stalemate";
7372                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7373                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7374                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7375                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7376                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7377                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7378                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7379                                                                         EP_CHECKMATE : EP_WINS);
7380                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7381                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7382                 }
7383                 break;
7384               case MT_CHECKMATE:
7385                 reason = "Xboard adjudication: Checkmate";
7386                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7387                 break;
7388             }
7389
7390                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7391                     case EP_STALEMATE:
7392                         result = GameIsDrawn; break;
7393                     case EP_CHECKMATE:
7394                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7395                     case EP_WINS:
7396                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7397                     default:
7398                         result = EndOfFile;
7399                 }
7400                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7401                     if(engineOpponent)
7402                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7403                     GameEnds( result, reason, GE_XBOARD );
7404                     return 1;
7405                 }
7406
7407                 /* Next absolutely insufficient mating material. */
7408                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7409                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7410                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7411
7412                      /* always flag draws, for judging claims */
7413                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7414
7415                      if(canAdjudicate && appData.materialDraws) {
7416                          /* but only adjudicate them if adjudication enabled */
7417                          if(engineOpponent) {
7418                            SendToProgram("force\n", engineOpponent); // suppress reply
7419                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7420                          }
7421                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7422                          return 1;
7423                      }
7424                 }
7425
7426                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7427                 if(gameInfo.variant == VariantXiangqi ?
7428                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7429                  : nrW + nrB == 4 &&
7430                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7431                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7432                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7433                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7434                    ) ) {
7435                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7436                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7437                           if(engineOpponent) {
7438                             SendToProgram("force\n", engineOpponent); // suppress reply
7439                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7440                           }
7441                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7442                           return 1;
7443                      }
7444                 } else moveCount = 6;
7445             }
7446         if (appData.debugMode) { int i;
7447             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7448                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7449                     appData.drawRepeats);
7450             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7451               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7452
7453         }
7454
7455         // Repetition draws and 50-move rule can be applied independently of legality testing
7456
7457                 /* Check for rep-draws */
7458                 count = 0;
7459                 for(k = forwardMostMove-2;
7460                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7461                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7462                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7463                     k-=2)
7464                 {   int rights=0;
7465                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7466                         /* compare castling rights */
7467                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7468                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7469                                 rights++; /* King lost rights, while rook still had them */
7470                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7471                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7472                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7473                                    rights++; /* but at least one rook lost them */
7474                         }
7475                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7476                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7477                                 rights++;
7478                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7479                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7480                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7481                                    rights++;
7482                         }
7483                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7484                             && appData.drawRepeats > 1) {
7485                              /* adjudicate after user-specified nr of repeats */
7486                              int result = GameIsDrawn;
7487                              char *details = "XBoard adjudication: repetition draw";
7488                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7489                                 // [HGM] xiangqi: check for forbidden perpetuals
7490                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7491                                 for(m=forwardMostMove; m>k; m-=2) {
7492                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7493                                         ourPerpetual = 0; // the current mover did not always check
7494                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7495                                         hisPerpetual = 0; // the opponent did not always check
7496                                 }
7497                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7498                                                                         ourPerpetual, hisPerpetual);
7499                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7500                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7501                                     details = "Xboard adjudication: perpetual checking";
7502                                 } else
7503                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7504                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7505                                 } else
7506                                 // Now check for perpetual chases
7507                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7508                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7509                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7510                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7511                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7512                                         details = "Xboard adjudication: perpetual chasing";
7513                                     } else
7514                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7515                                         break; // Abort repetition-checking loop.
7516                                 }
7517                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7518                              }
7519                              if(engineOpponent) {
7520                                SendToProgram("force\n", engineOpponent); // suppress reply
7521                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7522                              }
7523                              GameEnds( result, details, GE_XBOARD );
7524                              return 1;
7525                         }
7526                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7527                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7528                     }
7529                 }
7530
7531                 /* Now we test for 50-move draws. Determine ply count */
7532                 count = forwardMostMove;
7533                 /* look for last irreversble move */
7534                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7535                     count--;
7536                 /* if we hit starting position, add initial plies */
7537                 if( count == backwardMostMove )
7538                     count -= initialRulePlies;
7539                 count = forwardMostMove - count;
7540                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7541                         // adjust reversible move counter for checks in Xiangqi
7542                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7543                         if(i < backwardMostMove) i = backwardMostMove;
7544                         while(i <= forwardMostMove) {
7545                                 lastCheck = inCheck; // check evasion does not count
7546                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7547                                 if(inCheck || lastCheck) count--; // check does not count
7548                                 i++;
7549                         }
7550                 }
7551                 if( count >= 100)
7552                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7553                          /* this is used to judge if draw claims are legal */
7554                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7555                          if(engineOpponent) {
7556                            SendToProgram("force\n", engineOpponent); // suppress reply
7557                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7558                          }
7559                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7560                          return 1;
7561                 }
7562
7563                 /* if draw offer is pending, treat it as a draw claim
7564                  * when draw condition present, to allow engines a way to
7565                  * claim draws before making their move to avoid a race
7566                  * condition occurring after their move
7567                  */
7568                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7569                          char *p = NULL;
7570                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7571                              p = "Draw claim: 50-move rule";
7572                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7573                              p = "Draw claim: 3-fold repetition";
7574                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7575                              p = "Draw claim: insufficient mating material";
7576                          if( p != NULL && canAdjudicate) {
7577                              if(engineOpponent) {
7578                                SendToProgram("force\n", engineOpponent); // suppress reply
7579                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7580                              }
7581                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7582                              return 1;
7583                          }
7584                 }
7585
7586                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7587                     if(engineOpponent) {
7588                       SendToProgram("force\n", engineOpponent); // suppress reply
7589                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7590                     }
7591                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7592                     return 1;
7593                 }
7594         return 0;
7595 }
7596
7597 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7598 {   // [HGM] book: this routine intercepts moves to simulate book replies
7599     char *bookHit = NULL;
7600
7601     //first determine if the incoming move brings opponent into his book
7602     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7603         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7604     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7605     if(bookHit != NULL && !cps->bookSuspend) {
7606         // make sure opponent is not going to reply after receiving move to book position
7607         SendToProgram("force\n", cps);
7608         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7609     }
7610     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7611     // now arrange restart after book miss
7612     if(bookHit) {
7613         // after a book hit we never send 'go', and the code after the call to this routine
7614         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7615         char buf[MSG_SIZ], *move = bookHit;
7616         if(cps->useSAN) {
7617             int fromX, fromY, toX, toY;
7618             char promoChar;
7619             ChessMove moveType;
7620             move = buf + 30;
7621             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7622                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7623                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7624                                     PosFlags(forwardMostMove),
7625                                     fromY, fromX, toY, toX, promoChar, move);
7626             } else {
7627                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7628                 bookHit = NULL;
7629             }
7630         }
7631         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7632         SendToProgram(buf, cps);
7633         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7634     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7635         SendToProgram("go\n", cps);
7636         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7637     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7638         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7639             SendToProgram("go\n", cps);
7640         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7641     }
7642     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7643 }
7644
7645 char *savedMessage;
7646 ChessProgramState *savedState;
7647 void DeferredBookMove(void)
7648 {
7649         if(savedState->lastPing != savedState->lastPong)
7650                     ScheduleDelayedEvent(DeferredBookMove, 10);
7651         else
7652         HandleMachineMove(savedMessage, savedState);
7653 }
7654
7655 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7656
7657 void
7658 HandleMachineMove(message, cps)
7659      char *message;
7660      ChessProgramState *cps;
7661 {
7662     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7663     char realname[MSG_SIZ];
7664     int fromX, fromY, toX, toY;
7665     ChessMove moveType;
7666     char promoChar;
7667     char *p;
7668     int machineWhite;
7669     char *bookHit;
7670
7671     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7672         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7673         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7674             DisplayError(_("Invalid pairing from pairing engine"), 0);
7675             return;
7676         }
7677         pairingReceived = 1;
7678         NextMatchGame();
7679         return; // Skim the pairing messages here.
7680     }
7681
7682     cps->userError = 0;
7683
7684 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7685     /*
7686      * Kludge to ignore BEL characters
7687      */
7688     while (*message == '\007') message++;
7689
7690     /*
7691      * [HGM] engine debug message: ignore lines starting with '#' character
7692      */
7693     if(cps->debug && *message == '#') return;
7694
7695     /*
7696      * Look for book output
7697      */
7698     if (cps == &first && bookRequested) {
7699         if (message[0] == '\t' || message[0] == ' ') {
7700             /* Part of the book output is here; append it */
7701             strcat(bookOutput, message);
7702             strcat(bookOutput, "  \n");
7703             return;
7704         } else if (bookOutput[0] != NULLCHAR) {
7705             /* All of book output has arrived; display it */
7706             char *p = bookOutput;
7707             while (*p != NULLCHAR) {
7708                 if (*p == '\t') *p = ' ';
7709                 p++;
7710             }
7711             DisplayInformation(bookOutput);
7712             bookRequested = FALSE;
7713             /* Fall through to parse the current output */
7714         }
7715     }
7716
7717     /*
7718      * Look for machine move.
7719      */
7720     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7721         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7722     {
7723         /* This method is only useful on engines that support ping */
7724         if (cps->lastPing != cps->lastPong) {
7725           if (gameMode == BeginningOfGame) {
7726             /* Extra move from before last new; ignore */
7727             if (appData.debugMode) {
7728                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7729             }
7730           } else {
7731             if (appData.debugMode) {
7732                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7733                         cps->which, gameMode);
7734             }
7735
7736             SendToProgram("undo\n", cps);
7737           }
7738           return;
7739         }
7740
7741         switch (gameMode) {
7742           case BeginningOfGame:
7743             /* Extra move from before last reset; ignore */
7744             if (appData.debugMode) {
7745                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7746             }
7747             return;
7748
7749           case EndOfGame:
7750           case IcsIdle:
7751           default:
7752             /* Extra move after we tried to stop.  The mode test is
7753                not a reliable way of detecting this problem, but it's
7754                the best we can do on engines that don't support ping.
7755             */
7756             if (appData.debugMode) {
7757                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7758                         cps->which, gameMode);
7759             }
7760             SendToProgram("undo\n", cps);
7761             return;
7762
7763           case MachinePlaysWhite:
7764           case IcsPlayingWhite:
7765             machineWhite = TRUE;
7766             break;
7767
7768           case MachinePlaysBlack:
7769           case IcsPlayingBlack:
7770             machineWhite = FALSE;
7771             break;
7772
7773           case TwoMachinesPlay:
7774             machineWhite = (cps->twoMachinesColor[0] == 'w');
7775             break;
7776         }
7777         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7778             if (appData.debugMode) {
7779                 fprintf(debugFP,
7780                         "Ignoring move out of turn by %s, gameMode %d"
7781                         ", forwardMost %d\n",
7782                         cps->which, gameMode, forwardMostMove);
7783             }
7784             return;
7785         }
7786
7787     if (appData.debugMode) { int f = forwardMostMove;
7788         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7789                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7790                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7791     }
7792         if(cps->alphaRank) AlphaRank(machineMove, 4);
7793         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7794                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7795             /* Machine move could not be parsed; ignore it. */
7796           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7797                     machineMove, _(cps->which));
7798             DisplayError(buf1, 0);
7799             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7800                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7801             if (gameMode == TwoMachinesPlay) {
7802               GameEnds(machineWhite ? BlackWins : WhiteWins,
7803                        buf1, GE_XBOARD);
7804             }
7805             return;
7806         }
7807
7808         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7809         /* So we have to redo legality test with true e.p. status here,  */
7810         /* to make sure an illegal e.p. capture does not slip through,   */
7811         /* to cause a forfeit on a justified illegal-move complaint      */
7812         /* of the opponent.                                              */
7813         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7814            ChessMove moveType;
7815            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7816                              fromY, fromX, toY, toX, promoChar);
7817             if (appData.debugMode) {
7818                 int i;
7819                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7820                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7821                 fprintf(debugFP, "castling rights\n");
7822             }
7823             if(moveType == IllegalMove) {
7824               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7825                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7826                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7827                            buf1, GE_XBOARD);
7828                 return;
7829            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7830            /* [HGM] Kludge to handle engines that send FRC-style castling
7831               when they shouldn't (like TSCP-Gothic) */
7832            switch(moveType) {
7833              case WhiteASideCastleFR:
7834              case BlackASideCastleFR:
7835                toX+=2;
7836                currentMoveString[2]++;
7837                break;
7838              case WhiteHSideCastleFR:
7839              case BlackHSideCastleFR:
7840                toX--;
7841                currentMoveString[2]--;
7842                break;
7843              default: ; // nothing to do, but suppresses warning of pedantic compilers
7844            }
7845         }
7846         hintRequested = FALSE;
7847         lastHint[0] = NULLCHAR;
7848         bookRequested = FALSE;
7849         /* Program may be pondering now */
7850         cps->maybeThinking = TRUE;
7851         if (cps->sendTime == 2) cps->sendTime = 1;
7852         if (cps->offeredDraw) cps->offeredDraw--;
7853
7854         /* [AS] Save move info*/
7855         pvInfoList[ forwardMostMove ].score = programStats.score;
7856         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7857         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7858
7859         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7860
7861         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7862         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7863             int count = 0;
7864
7865             while( count < adjudicateLossPlies ) {
7866                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7867
7868                 if( count & 1 ) {
7869                     score = -score; /* Flip score for winning side */
7870                 }
7871
7872                 if( score > adjudicateLossThreshold ) {
7873                     break;
7874                 }
7875
7876                 count++;
7877             }
7878
7879             if( count >= adjudicateLossPlies ) {
7880                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7881
7882                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7883                     "Xboard adjudication",
7884                     GE_XBOARD );
7885
7886                 return;
7887             }
7888         }
7889
7890         if(Adjudicate(cps)) {
7891             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7892             return; // [HGM] adjudicate: for all automatic game ends
7893         }
7894
7895 #if ZIPPY
7896         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7897             first.initDone) {
7898           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7899                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7900                 SendToICS("draw ");
7901                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7902           }
7903           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7904           ics_user_moved = 1;
7905           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7906                 char buf[3*MSG_SIZ];
7907
7908                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7909                         programStats.score / 100.,
7910                         programStats.depth,
7911                         programStats.time / 100.,
7912                         (unsigned int)programStats.nodes,
7913                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7914                         programStats.movelist);
7915                 SendToICS(buf);
7916 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7917           }
7918         }
7919 #endif
7920
7921         /* [AS] Clear stats for next move */
7922         ClearProgramStats();
7923         thinkOutput[0] = NULLCHAR;
7924         hiddenThinkOutputState = 0;
7925
7926         bookHit = NULL;
7927         if (gameMode == TwoMachinesPlay) {
7928             /* [HGM] relaying draw offers moved to after reception of move */
7929             /* and interpreting offer as claim if it brings draw condition */
7930             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7931                 SendToProgram("draw\n", cps->other);
7932             }
7933             if (cps->other->sendTime) {
7934                 SendTimeRemaining(cps->other,
7935                                   cps->other->twoMachinesColor[0] == 'w');
7936             }
7937             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7938             if (firstMove && !bookHit) {
7939                 firstMove = FALSE;
7940                 if (cps->other->useColors) {
7941                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7942                 }
7943                 SendToProgram("go\n", cps->other);
7944             }
7945             cps->other->maybeThinking = TRUE;
7946         }
7947
7948         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7949
7950         if (!pausing && appData.ringBellAfterMoves) {
7951             RingBell();
7952         }
7953
7954         /*
7955          * Reenable menu items that were disabled while
7956          * machine was thinking
7957          */
7958         if (gameMode != TwoMachinesPlay)
7959             SetUserThinkingEnables();
7960
7961         // [HGM] book: after book hit opponent has received move and is now in force mode
7962         // force the book reply into it, and then fake that it outputted this move by jumping
7963         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7964         if(bookHit) {
7965                 static char bookMove[MSG_SIZ]; // a bit generous?
7966
7967                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7968                 strcat(bookMove, bookHit);
7969                 message = bookMove;
7970                 cps = cps->other;
7971                 programStats.nodes = programStats.depth = programStats.time =
7972                 programStats.score = programStats.got_only_move = 0;
7973                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7974
7975                 if(cps->lastPing != cps->lastPong) {
7976                     savedMessage = message; // args for deferred call
7977                     savedState = cps;
7978                     ScheduleDelayedEvent(DeferredBookMove, 10);
7979                     return;
7980                 }
7981                 goto FakeBookMove;
7982         }
7983
7984         return;
7985     }
7986
7987     /* Set special modes for chess engines.  Later something general
7988      *  could be added here; for now there is just one kludge feature,
7989      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7990      *  when "xboard" is given as an interactive command.
7991      */
7992     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7993         cps->useSigint = FALSE;
7994         cps->useSigterm = FALSE;
7995     }
7996     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7997       ParseFeatures(message+8, cps);
7998       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7999     }
8000
8001     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8002       int dummy, s=6; char buf[MSG_SIZ];
8003       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8004       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8005       ParseFEN(boards[0], &dummy, message+s);
8006       DrawPosition(TRUE, boards[0]);
8007       startedFromSetupPosition = TRUE;
8008       return;
8009     }
8010     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8011      * want this, I was asked to put it in, and obliged.
8012      */
8013     if (!strncmp(message, "setboard ", 9)) {
8014         Board initial_position;
8015
8016         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8017
8018         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8019             DisplayError(_("Bad FEN received from engine"), 0);
8020             return ;
8021         } else {
8022            Reset(TRUE, FALSE);
8023            CopyBoard(boards[0], initial_position);
8024            initialRulePlies = FENrulePlies;
8025            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8026            else gameMode = MachinePlaysBlack;
8027            DrawPosition(FALSE, boards[currentMove]);
8028         }
8029         return;
8030     }
8031
8032     /*
8033      * Look for communication commands
8034      */
8035     if (!strncmp(message, "telluser ", 9)) {
8036         if(message[9] == '\\' && message[10] == '\\')
8037             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8038         DisplayNote(message + 9);
8039         return;
8040     }
8041     if (!strncmp(message, "tellusererror ", 14)) {
8042         cps->userError = 1;
8043         if(message[14] == '\\' && message[15] == '\\')
8044             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8045         DisplayError(message + 14, 0);
8046         return;
8047     }
8048     if (!strncmp(message, "tellopponent ", 13)) {
8049       if (appData.icsActive) {
8050         if (loggedOn) {
8051           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8052           SendToICS(buf1);
8053         }
8054       } else {
8055         DisplayNote(message + 13);
8056       }
8057       return;
8058     }
8059     if (!strncmp(message, "tellothers ", 11)) {
8060       if (appData.icsActive) {
8061         if (loggedOn) {
8062           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8063           SendToICS(buf1);
8064         }
8065       }
8066       return;
8067     }
8068     if (!strncmp(message, "tellall ", 8)) {
8069       if (appData.icsActive) {
8070         if (loggedOn) {
8071           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8072           SendToICS(buf1);
8073         }
8074       } else {
8075         DisplayNote(message + 8);
8076       }
8077       return;
8078     }
8079     if (strncmp(message, "warning", 7) == 0) {
8080         /* Undocumented feature, use tellusererror in new code */
8081         DisplayError(message, 0);
8082         return;
8083     }
8084     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8085         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8086         strcat(realname, " query");
8087         AskQuestion(realname, buf2, buf1, cps->pr);
8088         return;
8089     }
8090     /* Commands from the engine directly to ICS.  We don't allow these to be
8091      *  sent until we are logged on. Crafty kibitzes have been known to
8092      *  interfere with the login process.
8093      */
8094     if (loggedOn) {
8095         if (!strncmp(message, "tellics ", 8)) {
8096             SendToICS(message + 8);
8097             SendToICS("\n");
8098             return;
8099         }
8100         if (!strncmp(message, "tellicsnoalias ", 15)) {
8101             SendToICS(ics_prefix);
8102             SendToICS(message + 15);
8103             SendToICS("\n");
8104             return;
8105         }
8106         /* The following are for backward compatibility only */
8107         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8108             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8109             SendToICS(ics_prefix);
8110             SendToICS(message);
8111             SendToICS("\n");
8112             return;
8113         }
8114     }
8115     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8116         return;
8117     }
8118     /*
8119      * If the move is illegal, cancel it and redraw the board.
8120      * Also deal with other error cases.  Matching is rather loose
8121      * here to accommodate engines written before the spec.
8122      */
8123     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8124         strncmp(message, "Error", 5) == 0) {
8125         if (StrStr(message, "name") ||
8126             StrStr(message, "rating") || StrStr(message, "?") ||
8127             StrStr(message, "result") || StrStr(message, "board") ||
8128             StrStr(message, "bk") || StrStr(message, "computer") ||
8129             StrStr(message, "variant") || StrStr(message, "hint") ||
8130             StrStr(message, "random") || StrStr(message, "depth") ||
8131             StrStr(message, "accepted")) {
8132             return;
8133         }
8134         if (StrStr(message, "protover")) {
8135           /* Program is responding to input, so it's apparently done
8136              initializing, and this error message indicates it is
8137              protocol version 1.  So we don't need to wait any longer
8138              for it to initialize and send feature commands. */
8139           FeatureDone(cps, 1);
8140           cps->protocolVersion = 1;
8141           return;
8142         }
8143         cps->maybeThinking = FALSE;
8144
8145         if (StrStr(message, "draw")) {
8146             /* Program doesn't have "draw" command */
8147             cps->sendDrawOffers = 0;
8148             return;
8149         }
8150         if (cps->sendTime != 1 &&
8151             (StrStr(message, "time") || StrStr(message, "otim"))) {
8152           /* Program apparently doesn't have "time" or "otim" command */
8153           cps->sendTime = 0;
8154           return;
8155         }
8156         if (StrStr(message, "analyze")) {
8157             cps->analysisSupport = FALSE;
8158             cps->analyzing = FALSE;
8159             Reset(FALSE, TRUE);
8160             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8161             DisplayError(buf2, 0);
8162             return;
8163         }
8164         if (StrStr(message, "(no matching move)st")) {
8165           /* Special kludge for GNU Chess 4 only */
8166           cps->stKludge = TRUE;
8167           SendTimeControl(cps, movesPerSession, timeControl,
8168                           timeIncrement, appData.searchDepth,
8169                           searchTime);
8170           return;
8171         }
8172         if (StrStr(message, "(no matching move)sd")) {
8173           /* Special kludge for GNU Chess 4 only */
8174           cps->sdKludge = TRUE;
8175           SendTimeControl(cps, movesPerSession, timeControl,
8176                           timeIncrement, appData.searchDepth,
8177                           searchTime);
8178           return;
8179         }
8180         if (!StrStr(message, "llegal")) {
8181             return;
8182         }
8183         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8184             gameMode == IcsIdle) return;
8185         if (forwardMostMove <= backwardMostMove) return;
8186         if (pausing) PauseEvent();
8187       if(appData.forceIllegal) {
8188             // [HGM] illegal: machine refused move; force position after move into it
8189           SendToProgram("force\n", cps);
8190           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8191                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8192                 // when black is to move, while there might be nothing on a2 or black
8193                 // might already have the move. So send the board as if white has the move.
8194                 // But first we must change the stm of the engine, as it refused the last move
8195                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8196                 if(WhiteOnMove(forwardMostMove)) {
8197                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8198                     SendBoard(cps, forwardMostMove); // kludgeless board
8199                 } else {
8200                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8201                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8202                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8203                 }
8204           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8205             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8206                  gameMode == TwoMachinesPlay)
8207               SendToProgram("go\n", cps);
8208             return;
8209       } else
8210         if (gameMode == PlayFromGameFile) {
8211             /* Stop reading this game file */
8212             gameMode = EditGame;
8213             ModeHighlight();
8214         }
8215         /* [HGM] illegal-move claim should forfeit game when Xboard */
8216         /* only passes fully legal moves                            */
8217         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8218             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8219                                 "False illegal-move claim", GE_XBOARD );
8220             return; // do not take back move we tested as valid
8221         }
8222         currentMove = forwardMostMove-1;
8223         DisplayMove(currentMove-1); /* before DisplayMoveError */
8224         SwitchClocks(forwardMostMove-1); // [HGM] race
8225         DisplayBothClocks();
8226         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8227                 parseList[currentMove], _(cps->which));
8228         DisplayMoveError(buf1);
8229         DrawPosition(FALSE, boards[currentMove]);
8230         return;
8231     }
8232     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8233         /* Program has a broken "time" command that
8234            outputs a string not ending in newline.
8235            Don't use it. */
8236         cps->sendTime = 0;
8237     }
8238
8239     /*
8240      * If chess program startup fails, exit with an error message.
8241      * Attempts to recover here are futile.
8242      */
8243     if ((StrStr(message, "unknown host") != NULL)
8244         || (StrStr(message, "No remote directory") != NULL)
8245         || (StrStr(message, "not found") != NULL)
8246         || (StrStr(message, "No such file") != NULL)
8247         || (StrStr(message, "can't alloc") != NULL)
8248         || (StrStr(message, "Permission denied") != NULL)) {
8249
8250         cps->maybeThinking = FALSE;
8251         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8252                 _(cps->which), cps->program, cps->host, message);
8253         RemoveInputSource(cps->isr);
8254         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8255             if(cps == &first) appData.noChessProgram = TRUE;
8256             DisplayError(buf1, 0);
8257         }
8258         return;
8259     }
8260
8261     /*
8262      * Look for hint output
8263      */
8264     if (sscanf(message, "Hint: %s", buf1) == 1) {
8265         if (cps == &first && hintRequested) {
8266             hintRequested = FALSE;
8267             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8268                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8269                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8270                                     PosFlags(forwardMostMove),
8271                                     fromY, fromX, toY, toX, promoChar, buf1);
8272                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8273                 DisplayInformation(buf2);
8274             } else {
8275                 /* Hint move could not be parsed!? */
8276               snprintf(buf2, sizeof(buf2),
8277                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8278                         buf1, _(cps->which));
8279                 DisplayError(buf2, 0);
8280             }
8281         } else {
8282           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8283         }
8284         return;
8285     }
8286
8287     /*
8288      * Ignore other messages if game is not in progress
8289      */
8290     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8291         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8292
8293     /*
8294      * look for win, lose, draw, or draw offer
8295      */
8296     if (strncmp(message, "1-0", 3) == 0) {
8297         char *p, *q, *r = "";
8298         p = strchr(message, '{');
8299         if (p) {
8300             q = strchr(p, '}');
8301             if (q) {
8302                 *q = NULLCHAR;
8303                 r = p + 1;
8304             }
8305         }
8306         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8307         return;
8308     } else if (strncmp(message, "0-1", 3) == 0) {
8309         char *p, *q, *r = "";
8310         p = strchr(message, '{');
8311         if (p) {
8312             q = strchr(p, '}');
8313             if (q) {
8314                 *q = NULLCHAR;
8315                 r = p + 1;
8316             }
8317         }
8318         /* Kludge for Arasan 4.1 bug */
8319         if (strcmp(r, "Black resigns") == 0) {
8320             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8321             return;
8322         }
8323         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8324         return;
8325     } else if (strncmp(message, "1/2", 3) == 0) {
8326         char *p, *q, *r = "";
8327         p = strchr(message, '{');
8328         if (p) {
8329             q = strchr(p, '}');
8330             if (q) {
8331                 *q = NULLCHAR;
8332                 r = p + 1;
8333             }
8334         }
8335
8336         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8337         return;
8338
8339     } else if (strncmp(message, "White resign", 12) == 0) {
8340         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8341         return;
8342     } else if (strncmp(message, "Black resign", 12) == 0) {
8343         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8344         return;
8345     } else if (strncmp(message, "White matches", 13) == 0 ||
8346                strncmp(message, "Black matches", 13) == 0   ) {
8347         /* [HGM] ignore GNUShogi noises */
8348         return;
8349     } else if (strncmp(message, "White", 5) == 0 &&
8350                message[5] != '(' &&
8351                StrStr(message, "Black") == NULL) {
8352         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8353         return;
8354     } else if (strncmp(message, "Black", 5) == 0 &&
8355                message[5] != '(') {
8356         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8357         return;
8358     } else if (strcmp(message, "resign") == 0 ||
8359                strcmp(message, "computer resigns") == 0) {
8360         switch (gameMode) {
8361           case MachinePlaysBlack:
8362           case IcsPlayingBlack:
8363             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8364             break;
8365           case MachinePlaysWhite:
8366           case IcsPlayingWhite:
8367             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8368             break;
8369           case TwoMachinesPlay:
8370             if (cps->twoMachinesColor[0] == 'w')
8371               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8372             else
8373               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8374             break;
8375           default:
8376             /* can't happen */
8377             break;
8378         }
8379         return;
8380     } else if (strncmp(message, "opponent mates", 14) == 0) {
8381         switch (gameMode) {
8382           case MachinePlaysBlack:
8383           case IcsPlayingBlack:
8384             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8385             break;
8386           case MachinePlaysWhite:
8387           case IcsPlayingWhite:
8388             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8389             break;
8390           case TwoMachinesPlay:
8391             if (cps->twoMachinesColor[0] == 'w')
8392               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8393             else
8394               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8395             break;
8396           default:
8397             /* can't happen */
8398             break;
8399         }
8400         return;
8401     } else if (strncmp(message, "computer mates", 14) == 0) {
8402         switch (gameMode) {
8403           case MachinePlaysBlack:
8404           case IcsPlayingBlack:
8405             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8406             break;
8407           case MachinePlaysWhite:
8408           case IcsPlayingWhite:
8409             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8410             break;
8411           case TwoMachinesPlay:
8412             if (cps->twoMachinesColor[0] == 'w')
8413               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8414             else
8415               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8416             break;
8417           default:
8418             /* can't happen */
8419             break;
8420         }
8421         return;
8422     } else if (strncmp(message, "checkmate", 9) == 0) {
8423         if (WhiteOnMove(forwardMostMove)) {
8424             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8425         } else {
8426             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8427         }
8428         return;
8429     } else if (strstr(message, "Draw") != NULL ||
8430                strstr(message, "game is a draw") != NULL) {
8431         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8432         return;
8433     } else if (strstr(message, "offer") != NULL &&
8434                strstr(message, "draw") != NULL) {
8435 #if ZIPPY
8436         if (appData.zippyPlay && first.initDone) {
8437             /* Relay offer to ICS */
8438             SendToICS(ics_prefix);
8439             SendToICS("draw\n");
8440         }
8441 #endif
8442         cps->offeredDraw = 2; /* valid until this engine moves twice */
8443         if (gameMode == TwoMachinesPlay) {
8444             if (cps->other->offeredDraw) {
8445                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8446             /* [HGM] in two-machine mode we delay relaying draw offer      */
8447             /* until after we also have move, to see if it is really claim */
8448             }
8449         } else if (gameMode == MachinePlaysWhite ||
8450                    gameMode == MachinePlaysBlack) {
8451           if (userOfferedDraw) {
8452             DisplayInformation(_("Machine accepts your draw offer"));
8453             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8454           } else {
8455             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8456           }
8457         }
8458     }
8459
8460
8461     /*
8462      * Look for thinking output
8463      */
8464     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8465           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8466                                 ) {
8467         int plylev, mvleft, mvtot, curscore, time;
8468         char mvname[MOVE_LEN];
8469         u64 nodes; // [DM]
8470         char plyext;
8471         int ignore = FALSE;
8472         int prefixHint = FALSE;
8473         mvname[0] = NULLCHAR;
8474
8475         switch (gameMode) {
8476           case MachinePlaysBlack:
8477           case IcsPlayingBlack:
8478             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8479             break;
8480           case MachinePlaysWhite:
8481           case IcsPlayingWhite:
8482             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8483             break;
8484           case AnalyzeMode:
8485           case AnalyzeFile:
8486             break;
8487           case IcsObserving: /* [DM] icsEngineAnalyze */
8488             if (!appData.icsEngineAnalyze) ignore = TRUE;
8489             break;
8490           case TwoMachinesPlay:
8491             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8492                 ignore = TRUE;
8493             }
8494             break;
8495           default:
8496             ignore = TRUE;
8497             break;
8498         }
8499
8500         if (!ignore) {
8501             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8502             buf1[0] = NULLCHAR;
8503             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8504                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8505
8506                 if (plyext != ' ' && plyext != '\t') {
8507                     time *= 100;
8508                 }
8509
8510                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8511                 if( cps->scoreIsAbsolute &&
8512                     ( gameMode == MachinePlaysBlack ||
8513                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8514                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8515                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8516                      !WhiteOnMove(currentMove)
8517                     ) )
8518                 {
8519                     curscore = -curscore;
8520                 }
8521
8522
8523                 tempStats.depth = plylev;
8524                 tempStats.nodes = nodes;
8525                 tempStats.time = time;
8526                 tempStats.score = curscore;
8527                 tempStats.got_only_move = 0;
8528
8529                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8530                         int ticklen;
8531
8532                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8533                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8534                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8535                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8536                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8537                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8538                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8539                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8540                 }
8541
8542                 /* Buffer overflow protection */
8543                 if (buf1[0] != NULLCHAR) {
8544                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8545                         && appData.debugMode) {
8546                         fprintf(debugFP,
8547                                 "PV is too long; using the first %u bytes.\n",
8548                                 (unsigned) sizeof(tempStats.movelist) - 1);
8549                     }
8550
8551                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8552                 } else {
8553                     sprintf(tempStats.movelist, " no PV\n");
8554                 }
8555
8556                 if (tempStats.seen_stat) {
8557                     tempStats.ok_to_send = 1;
8558                 }
8559
8560                 if (strchr(tempStats.movelist, '(') != NULL) {
8561                     tempStats.line_is_book = 1;
8562                     tempStats.nr_moves = 0;
8563                     tempStats.moves_left = 0;
8564                 } else {
8565                     tempStats.line_is_book = 0;
8566                 }
8567
8568                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8569                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8570
8571                 SendProgramStatsToFrontend( cps, &tempStats );
8572
8573                 /*
8574                     [AS] Protect the thinkOutput buffer from overflow... this
8575                     is only useful if buf1 hasn't overflowed first!
8576                 */
8577                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8578                          plylev,
8579                          (gameMode == TwoMachinesPlay ?
8580                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8581                          ((double) curscore) / 100.0,
8582                          prefixHint ? lastHint : "",
8583                          prefixHint ? " " : "" );
8584
8585                 if( buf1[0] != NULLCHAR ) {
8586                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8587
8588                     if( strlen(buf1) > max_len ) {
8589                         if( appData.debugMode) {
8590                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8591                         }
8592                         buf1[max_len+1] = '\0';
8593                     }
8594
8595                     strcat( thinkOutput, buf1 );
8596                 }
8597
8598                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8599                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8600                     DisplayMove(currentMove - 1);
8601                 }
8602                 return;
8603
8604             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8605                 /* crafty (9.25+) says "(only move) <move>"
8606                  * if there is only 1 legal move
8607                  */
8608                 sscanf(p, "(only move) %s", buf1);
8609                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8610                 sprintf(programStats.movelist, "%s (only move)", buf1);
8611                 programStats.depth = 1;
8612                 programStats.nr_moves = 1;
8613                 programStats.moves_left = 1;
8614                 programStats.nodes = 1;
8615                 programStats.time = 1;
8616                 programStats.got_only_move = 1;
8617
8618                 /* Not really, but we also use this member to
8619                    mean "line isn't going to change" (Crafty
8620                    isn't searching, so stats won't change) */
8621                 programStats.line_is_book = 1;
8622
8623                 SendProgramStatsToFrontend( cps, &programStats );
8624
8625                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8626                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8627                     DisplayMove(currentMove - 1);
8628                 }
8629                 return;
8630             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8631                               &time, &nodes, &plylev, &mvleft,
8632                               &mvtot, mvname) >= 5) {
8633                 /* The stat01: line is from Crafty (9.29+) in response
8634                    to the "." command */
8635                 programStats.seen_stat = 1;
8636                 cps->maybeThinking = TRUE;
8637
8638                 if (programStats.got_only_move || !appData.periodicUpdates)
8639                   return;
8640
8641                 programStats.depth = plylev;
8642                 programStats.time = time;
8643                 programStats.nodes = nodes;
8644                 programStats.moves_left = mvleft;
8645                 programStats.nr_moves = mvtot;
8646                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8647                 programStats.ok_to_send = 1;
8648                 programStats.movelist[0] = '\0';
8649
8650                 SendProgramStatsToFrontend( cps, &programStats );
8651
8652                 return;
8653
8654             } else if (strncmp(message,"++",2) == 0) {
8655                 /* Crafty 9.29+ outputs this */
8656                 programStats.got_fail = 2;
8657                 return;
8658
8659             } else if (strncmp(message,"--",2) == 0) {
8660                 /* Crafty 9.29+ outputs this */
8661                 programStats.got_fail = 1;
8662                 return;
8663
8664             } else if (thinkOutput[0] != NULLCHAR &&
8665                        strncmp(message, "    ", 4) == 0) {
8666                 unsigned message_len;
8667
8668                 p = message;
8669                 while (*p && *p == ' ') p++;
8670
8671                 message_len = strlen( p );
8672
8673                 /* [AS] Avoid buffer overflow */
8674                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8675                     strcat(thinkOutput, " ");
8676                     strcat(thinkOutput, p);
8677                 }
8678
8679                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8680                     strcat(programStats.movelist, " ");
8681                     strcat(programStats.movelist, p);
8682                 }
8683
8684                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8685                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8686                     DisplayMove(currentMove - 1);
8687                 }
8688                 return;
8689             }
8690         }
8691         else {
8692             buf1[0] = NULLCHAR;
8693
8694             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8695                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8696             {
8697                 ChessProgramStats cpstats;
8698
8699                 if (plyext != ' ' && plyext != '\t') {
8700                     time *= 100;
8701                 }
8702
8703                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8704                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8705                     curscore = -curscore;
8706                 }
8707
8708                 cpstats.depth = plylev;
8709                 cpstats.nodes = nodes;
8710                 cpstats.time = time;
8711                 cpstats.score = curscore;
8712                 cpstats.got_only_move = 0;
8713                 cpstats.movelist[0] = '\0';
8714
8715                 if (buf1[0] != NULLCHAR) {
8716                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8717                 }
8718
8719                 cpstats.ok_to_send = 0;
8720                 cpstats.line_is_book = 0;
8721                 cpstats.nr_moves = 0;
8722                 cpstats.moves_left = 0;
8723
8724                 SendProgramStatsToFrontend( cps, &cpstats );
8725             }
8726         }
8727     }
8728 }
8729
8730
8731 /* Parse a game score from the character string "game", and
8732    record it as the history of the current game.  The game
8733    score is NOT assumed to start from the standard position.
8734    The display is not updated in any way.
8735    */
8736 void
8737 ParseGameHistory(game)
8738      char *game;
8739 {
8740     ChessMove moveType;
8741     int fromX, fromY, toX, toY, boardIndex;
8742     char promoChar;
8743     char *p, *q;
8744     char buf[MSG_SIZ];
8745
8746     if (appData.debugMode)
8747       fprintf(debugFP, "Parsing game history: %s\n", game);
8748
8749     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8750     gameInfo.site = StrSave(appData.icsHost);
8751     gameInfo.date = PGNDate();
8752     gameInfo.round = StrSave("-");
8753
8754     /* Parse out names of players */
8755     while (*game == ' ') game++;
8756     p = buf;
8757     while (*game != ' ') *p++ = *game++;
8758     *p = NULLCHAR;
8759     gameInfo.white = StrSave(buf);
8760     while (*game == ' ') game++;
8761     p = buf;
8762     while (*game != ' ' && *game != '\n') *p++ = *game++;
8763     *p = NULLCHAR;
8764     gameInfo.black = StrSave(buf);
8765
8766     /* Parse moves */
8767     boardIndex = blackPlaysFirst ? 1 : 0;
8768     yynewstr(game);
8769     for (;;) {
8770         yyboardindex = boardIndex;
8771         moveType = (ChessMove) Myylex();
8772         switch (moveType) {
8773           case IllegalMove:             /* maybe suicide chess, etc. */
8774   if (appData.debugMode) {
8775     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8776     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8777     setbuf(debugFP, NULL);
8778   }
8779           case WhitePromotion:
8780           case BlackPromotion:
8781           case WhiteNonPromotion:
8782           case BlackNonPromotion:
8783           case NormalMove:
8784           case WhiteCapturesEnPassant:
8785           case BlackCapturesEnPassant:
8786           case WhiteKingSideCastle:
8787           case WhiteQueenSideCastle:
8788           case BlackKingSideCastle:
8789           case BlackQueenSideCastle:
8790           case WhiteKingSideCastleWild:
8791           case WhiteQueenSideCastleWild:
8792           case BlackKingSideCastleWild:
8793           case BlackQueenSideCastleWild:
8794           /* PUSH Fabien */
8795           case WhiteHSideCastleFR:
8796           case WhiteASideCastleFR:
8797           case BlackHSideCastleFR:
8798           case BlackASideCastleFR:
8799           /* POP Fabien */
8800             fromX = currentMoveString[0] - AAA;
8801             fromY = currentMoveString[1] - ONE;
8802             toX = currentMoveString[2] - AAA;
8803             toY = currentMoveString[3] - ONE;
8804             promoChar = currentMoveString[4];
8805             break;
8806           case WhiteDrop:
8807           case BlackDrop:
8808             fromX = moveType == WhiteDrop ?
8809               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8810             (int) CharToPiece(ToLower(currentMoveString[0]));
8811             fromY = DROP_RANK;
8812             toX = currentMoveString[2] - AAA;
8813             toY = currentMoveString[3] - ONE;
8814             promoChar = NULLCHAR;
8815             break;
8816           case AmbiguousMove:
8817             /* bug? */
8818             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8819   if (appData.debugMode) {
8820     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8821     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8822     setbuf(debugFP, NULL);
8823   }
8824             DisplayError(buf, 0);
8825             return;
8826           case ImpossibleMove:
8827             /* bug? */
8828             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8829   if (appData.debugMode) {
8830     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8831     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8832     setbuf(debugFP, NULL);
8833   }
8834             DisplayError(buf, 0);
8835             return;
8836           case EndOfFile:
8837             if (boardIndex < backwardMostMove) {
8838                 /* Oops, gap.  How did that happen? */
8839                 DisplayError(_("Gap in move list"), 0);
8840                 return;
8841             }
8842             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8843             if (boardIndex > forwardMostMove) {
8844                 forwardMostMove = boardIndex;
8845             }
8846             return;
8847           case ElapsedTime:
8848             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8849                 strcat(parseList[boardIndex-1], " ");
8850                 strcat(parseList[boardIndex-1], yy_text);
8851             }
8852             continue;
8853           case Comment:
8854           case PGNTag:
8855           case NAG:
8856           default:
8857             /* ignore */
8858             continue;
8859           case WhiteWins:
8860           case BlackWins:
8861           case GameIsDrawn:
8862           case GameUnfinished:
8863             if (gameMode == IcsExamining) {
8864                 if (boardIndex < backwardMostMove) {
8865                     /* Oops, gap.  How did that happen? */
8866                     return;
8867                 }
8868                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8869                 return;
8870             }
8871             gameInfo.result = moveType;
8872             p = strchr(yy_text, '{');
8873             if (p == NULL) p = strchr(yy_text, '(');
8874             if (p == NULL) {
8875                 p = yy_text;
8876                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8877             } else {
8878                 q = strchr(p, *p == '{' ? '}' : ')');
8879                 if (q != NULL) *q = NULLCHAR;
8880                 p++;
8881             }
8882             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8883             gameInfo.resultDetails = StrSave(p);
8884             continue;
8885         }
8886         if (boardIndex >= forwardMostMove &&
8887             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8888             backwardMostMove = blackPlaysFirst ? 1 : 0;
8889             return;
8890         }
8891         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8892                                  fromY, fromX, toY, toX, promoChar,
8893                                  parseList[boardIndex]);
8894         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8895         /* currentMoveString is set as a side-effect of yylex */
8896         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8897         strcat(moveList[boardIndex], "\n");
8898         boardIndex++;
8899         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8900         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8901           case MT_NONE:
8902           case MT_STALEMATE:
8903           default:
8904             break;
8905           case MT_CHECK:
8906             if(gameInfo.variant != VariantShogi)
8907                 strcat(parseList[boardIndex - 1], "+");
8908             break;
8909           case MT_CHECKMATE:
8910           case MT_STAINMATE:
8911             strcat(parseList[boardIndex - 1], "#");
8912             break;
8913         }
8914     }
8915 }
8916
8917
8918 /* Apply a move to the given board  */
8919 void
8920 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8921      int fromX, fromY, toX, toY;
8922      int promoChar;
8923      Board board;
8924 {
8925   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8926   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8927
8928     /* [HGM] compute & store e.p. status and castling rights for new position */
8929     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8930
8931       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8932       oldEP = (signed char)board[EP_STATUS];
8933       board[EP_STATUS] = EP_NONE;
8934
8935       if( board[toY][toX] != EmptySquare )
8936            board[EP_STATUS] = EP_CAPTURE;
8937
8938   if (fromY == DROP_RANK) {
8939         /* must be first */
8940         piece = board[toY][toX] = (ChessSquare) fromX;
8941   } else {
8942       int i;
8943
8944       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8945            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8946                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8947       } else
8948       if( board[fromY][fromX] == WhitePawn ) {
8949            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8950                board[EP_STATUS] = EP_PAWN_MOVE;
8951            if( toY-fromY==2) {
8952                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8953                         gameInfo.variant != VariantBerolina || toX < fromX)
8954                       board[EP_STATUS] = toX | berolina;
8955                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8956                         gameInfo.variant != VariantBerolina || toX > fromX)
8957                       board[EP_STATUS] = toX;
8958            }
8959       } else
8960       if( board[fromY][fromX] == BlackPawn ) {
8961            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8962                board[EP_STATUS] = EP_PAWN_MOVE;
8963            if( toY-fromY== -2) {
8964                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8965                         gameInfo.variant != VariantBerolina || toX < fromX)
8966                       board[EP_STATUS] = toX | berolina;
8967                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8968                         gameInfo.variant != VariantBerolina || toX > fromX)
8969                       board[EP_STATUS] = toX;
8970            }
8971        }
8972
8973        for(i=0; i<nrCastlingRights; i++) {
8974            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8975               board[CASTLING][i] == toX   && castlingRank[i] == toY
8976              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8977        }
8978
8979      if (fromX == toX && fromY == toY) return;
8980
8981      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8982      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8983      if(gameInfo.variant == VariantKnightmate)
8984          king += (int) WhiteUnicorn - (int) WhiteKing;
8985
8986     /* Code added by Tord: */
8987     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8988     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8989         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8990       board[fromY][fromX] = EmptySquare;
8991       board[toY][toX] = EmptySquare;
8992       if((toX > fromX) != (piece == WhiteRook)) {
8993         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8994       } else {
8995         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8996       }
8997     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8998                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8999       board[fromY][fromX] = EmptySquare;
9000       board[toY][toX] = EmptySquare;
9001       if((toX > fromX) != (piece == BlackRook)) {
9002         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9003       } else {
9004         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9005       }
9006     /* End of code added by Tord */
9007
9008     } else if (board[fromY][fromX] == king
9009         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9010         && toY == fromY && toX > fromX+1) {
9011         board[fromY][fromX] = EmptySquare;
9012         board[toY][toX] = king;
9013         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9014         board[fromY][BOARD_RGHT-1] = EmptySquare;
9015     } else if (board[fromY][fromX] == king
9016         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9017                && toY == fromY && toX < fromX-1) {
9018         board[fromY][fromX] = EmptySquare;
9019         board[toY][toX] = king;
9020         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9021         board[fromY][BOARD_LEFT] = EmptySquare;
9022     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9023                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9024                && toY >= BOARD_HEIGHT-promoRank
9025                ) {
9026         /* white pawn promotion */
9027         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9028         if (board[toY][toX] == EmptySquare) {
9029             board[toY][toX] = WhiteQueen;
9030         }
9031         if(gameInfo.variant==VariantBughouse ||
9032            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9033             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9034         board[fromY][fromX] = EmptySquare;
9035     } else if ((fromY == BOARD_HEIGHT-4)
9036                && (toX != fromX)
9037                && gameInfo.variant != VariantXiangqi
9038                && gameInfo.variant != VariantBerolina
9039                && (board[fromY][fromX] == WhitePawn)
9040                && (board[toY][toX] == EmptySquare)) {
9041         board[fromY][fromX] = EmptySquare;
9042         board[toY][toX] = WhitePawn;
9043         captured = board[toY - 1][toX];
9044         board[toY - 1][toX] = EmptySquare;
9045     } else if ((fromY == BOARD_HEIGHT-4)
9046                && (toX == fromX)
9047                && gameInfo.variant == VariantBerolina
9048                && (board[fromY][fromX] == WhitePawn)
9049                && (board[toY][toX] == EmptySquare)) {
9050         board[fromY][fromX] = EmptySquare;
9051         board[toY][toX] = WhitePawn;
9052         if(oldEP & EP_BEROLIN_A) {
9053                 captured = board[fromY][fromX-1];
9054                 board[fromY][fromX-1] = EmptySquare;
9055         }else{  captured = board[fromY][fromX+1];
9056                 board[fromY][fromX+1] = EmptySquare;
9057         }
9058     } else if (board[fromY][fromX] == king
9059         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9060                && toY == fromY && toX > fromX+1) {
9061         board[fromY][fromX] = EmptySquare;
9062         board[toY][toX] = king;
9063         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9064         board[fromY][BOARD_RGHT-1] = EmptySquare;
9065     } else if (board[fromY][fromX] == king
9066         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9067                && toY == fromY && toX < fromX-1) {
9068         board[fromY][fromX] = EmptySquare;
9069         board[toY][toX] = king;
9070         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9071         board[fromY][BOARD_LEFT] = EmptySquare;
9072     } else if (fromY == 7 && fromX == 3
9073                && board[fromY][fromX] == BlackKing
9074                && toY == 7 && toX == 5) {
9075         board[fromY][fromX] = EmptySquare;
9076         board[toY][toX] = BlackKing;
9077         board[fromY][7] = EmptySquare;
9078         board[toY][4] = BlackRook;
9079     } else if (fromY == 7 && fromX == 3
9080                && board[fromY][fromX] == BlackKing
9081                && toY == 7 && toX == 1) {
9082         board[fromY][fromX] = EmptySquare;
9083         board[toY][toX] = BlackKing;
9084         board[fromY][0] = EmptySquare;
9085         board[toY][2] = BlackRook;
9086     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9087                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9088                && toY < promoRank
9089                ) {
9090         /* black pawn promotion */
9091         board[toY][toX] = CharToPiece(ToLower(promoChar));
9092         if (board[toY][toX] == EmptySquare) {
9093             board[toY][toX] = BlackQueen;
9094         }
9095         if(gameInfo.variant==VariantBughouse ||
9096            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9097             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9098         board[fromY][fromX] = EmptySquare;
9099     } else if ((fromY == 3)
9100                && (toX != fromX)
9101                && gameInfo.variant != VariantXiangqi
9102                && gameInfo.variant != VariantBerolina
9103                && (board[fromY][fromX] == BlackPawn)
9104                && (board[toY][toX] == EmptySquare)) {
9105         board[fromY][fromX] = EmptySquare;
9106         board[toY][toX] = BlackPawn;
9107         captured = board[toY + 1][toX];
9108         board[toY + 1][toX] = EmptySquare;
9109     } else if ((fromY == 3)
9110                && (toX == fromX)
9111                && gameInfo.variant == VariantBerolina
9112                && (board[fromY][fromX] == BlackPawn)
9113                && (board[toY][toX] == EmptySquare)) {
9114         board[fromY][fromX] = EmptySquare;
9115         board[toY][toX] = BlackPawn;
9116         if(oldEP & EP_BEROLIN_A) {
9117                 captured = board[fromY][fromX-1];
9118                 board[fromY][fromX-1] = EmptySquare;
9119         }else{  captured = board[fromY][fromX+1];
9120                 board[fromY][fromX+1] = EmptySquare;
9121         }
9122     } else {
9123         board[toY][toX] = board[fromY][fromX];
9124         board[fromY][fromX] = EmptySquare;
9125     }
9126   }
9127
9128     if (gameInfo.holdingsWidth != 0) {
9129
9130       /* !!A lot more code needs to be written to support holdings  */
9131       /* [HGM] OK, so I have written it. Holdings are stored in the */
9132       /* penultimate board files, so they are automaticlly stored   */
9133       /* in the game history.                                       */
9134       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9135                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9136         /* Delete from holdings, by decreasing count */
9137         /* and erasing image if necessary            */
9138         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9139         if(p < (int) BlackPawn) { /* white drop */
9140              p -= (int)WhitePawn;
9141                  p = PieceToNumber((ChessSquare)p);
9142              if(p >= gameInfo.holdingsSize) p = 0;
9143              if(--board[p][BOARD_WIDTH-2] <= 0)
9144                   board[p][BOARD_WIDTH-1] = EmptySquare;
9145              if((int)board[p][BOARD_WIDTH-2] < 0)
9146                         board[p][BOARD_WIDTH-2] = 0;
9147         } else {                  /* black drop */
9148              p -= (int)BlackPawn;
9149                  p = PieceToNumber((ChessSquare)p);
9150              if(p >= gameInfo.holdingsSize) p = 0;
9151              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9152                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9153              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9154                         board[BOARD_HEIGHT-1-p][1] = 0;
9155         }
9156       }
9157       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9158           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9159         /* [HGM] holdings: Add to holdings, if holdings exist */
9160         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9161                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9162                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9163         }
9164         p = (int) captured;
9165         if (p >= (int) BlackPawn) {
9166           p -= (int)BlackPawn;
9167           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9168                   /* in Shogi restore piece to its original  first */
9169                   captured = (ChessSquare) (DEMOTED captured);
9170                   p = DEMOTED p;
9171           }
9172           p = PieceToNumber((ChessSquare)p);
9173           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9174           board[p][BOARD_WIDTH-2]++;
9175           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9176         } else {
9177           p -= (int)WhitePawn;
9178           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9179                   captured = (ChessSquare) (DEMOTED captured);
9180                   p = DEMOTED p;
9181           }
9182           p = PieceToNumber((ChessSquare)p);
9183           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9184           board[BOARD_HEIGHT-1-p][1]++;
9185           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9186         }
9187       }
9188     } else if (gameInfo.variant == VariantAtomic) {
9189       if (captured != EmptySquare) {
9190         int y, x;
9191         for (y = toY-1; y <= toY+1; y++) {
9192           for (x = toX-1; x <= toX+1; x++) {
9193             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9194                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9195               board[y][x] = EmptySquare;
9196             }
9197           }
9198         }
9199         board[toY][toX] = EmptySquare;
9200       }
9201     }
9202     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9203         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9204     } else
9205     if(promoChar == '+') {
9206         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9207         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9208     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9209         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9210     }
9211     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9212                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9213         // [HGM] superchess: take promotion piece out of holdings
9214         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9215         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9216             if(!--board[k][BOARD_WIDTH-2])
9217                 board[k][BOARD_WIDTH-1] = EmptySquare;
9218         } else {
9219             if(!--board[BOARD_HEIGHT-1-k][1])
9220                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9221         }
9222     }
9223
9224 }
9225
9226 /* Updates forwardMostMove */
9227 void
9228 MakeMove(fromX, fromY, toX, toY, promoChar)
9229      int fromX, fromY, toX, toY;
9230      int promoChar;
9231 {
9232 //    forwardMostMove++; // [HGM] bare: moved downstream
9233
9234     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9235         int timeLeft; static int lastLoadFlag=0; int king, piece;
9236         piece = boards[forwardMostMove][fromY][fromX];
9237         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9238         if(gameInfo.variant == VariantKnightmate)
9239             king += (int) WhiteUnicorn - (int) WhiteKing;
9240         if(forwardMostMove == 0) {
9241             if(blackPlaysFirst)
9242                 fprintf(serverMoves, "%s;", second.tidy);
9243             fprintf(serverMoves, "%s;", first.tidy);
9244             if(!blackPlaysFirst)
9245                 fprintf(serverMoves, "%s;", second.tidy);
9246         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9247         lastLoadFlag = loadFlag;
9248         // print base move
9249         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9250         // print castling suffix
9251         if( toY == fromY && piece == king ) {
9252             if(toX-fromX > 1)
9253                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9254             if(fromX-toX >1)
9255                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9256         }
9257         // e.p. suffix
9258         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9259              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9260              boards[forwardMostMove][toY][toX] == EmptySquare
9261              && fromX != toX && fromY != toY)
9262                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9263         // promotion suffix
9264         if(promoChar != NULLCHAR)
9265                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9266         if(!loadFlag) {
9267             fprintf(serverMoves, "/%d/%d",
9268                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9269             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9270             else                      timeLeft = blackTimeRemaining/1000;
9271             fprintf(serverMoves, "/%d", timeLeft);
9272         }
9273         fflush(serverMoves);
9274     }
9275
9276     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9277       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9278                         0, 1);
9279       return;
9280     }
9281     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9282     if (commentList[forwardMostMove+1] != NULL) {
9283         free(commentList[forwardMostMove+1]);
9284         commentList[forwardMostMove+1] = NULL;
9285     }
9286     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9287     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9288     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9289     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9290     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9291     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9292     gameInfo.result = GameUnfinished;
9293     if (gameInfo.resultDetails != NULL) {
9294         free(gameInfo.resultDetails);
9295         gameInfo.resultDetails = NULL;
9296     }
9297     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9298                               moveList[forwardMostMove - 1]);
9299     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9300                              PosFlags(forwardMostMove - 1),
9301                              fromY, fromX, toY, toX, promoChar,
9302                              parseList[forwardMostMove - 1]);
9303     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9304       case MT_NONE:
9305       case MT_STALEMATE:
9306       default:
9307         break;
9308       case MT_CHECK:
9309         if(gameInfo.variant != VariantShogi)
9310             strcat(parseList[forwardMostMove - 1], "+");
9311         break;
9312       case MT_CHECKMATE:
9313       case MT_STAINMATE:
9314         strcat(parseList[forwardMostMove - 1], "#");
9315         break;
9316     }
9317     if (appData.debugMode) {
9318         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9319     }
9320
9321 }
9322
9323 /* Updates currentMove if not pausing */
9324 void
9325 ShowMove(fromX, fromY, toX, toY)
9326 {
9327     int instant = (gameMode == PlayFromGameFile) ?
9328         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9329     if(appData.noGUI) return;
9330     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9331         if (!instant) {
9332             if (forwardMostMove == currentMove + 1) {
9333                 AnimateMove(boards[forwardMostMove - 1],
9334                             fromX, fromY, toX, toY);
9335             }
9336             if (appData.highlightLastMove) {
9337                 SetHighlights(fromX, fromY, toX, toY);
9338             }
9339         }
9340         currentMove = forwardMostMove;
9341     }
9342
9343     if (instant) return;
9344
9345     DisplayMove(currentMove - 1);
9346     DrawPosition(FALSE, boards[currentMove]);
9347     DisplayBothClocks();
9348     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9349     DisplayBook(currentMove);
9350 }
9351
9352 void SendEgtPath(ChessProgramState *cps)
9353 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9354         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9355
9356         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9357
9358         while(*p) {
9359             char c, *q = name+1, *r, *s;
9360
9361             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9362             while(*p && *p != ',') *q++ = *p++;
9363             *q++ = ':'; *q = 0;
9364             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9365                 strcmp(name, ",nalimov:") == 0 ) {
9366                 // take nalimov path from the menu-changeable option first, if it is defined
9367               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9368                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9369             } else
9370             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9371                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9372                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9373                 s = r = StrStr(s, ":") + 1; // beginning of path info
9374                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9375                 c = *r; *r = 0;             // temporarily null-terminate path info
9376                     *--q = 0;               // strip of trailig ':' from name
9377                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9378                 *r = c;
9379                 SendToProgram(buf,cps);     // send egtbpath command for this format
9380             }
9381             if(*p == ',') p++; // read away comma to position for next format name
9382         }
9383 }
9384
9385 void
9386 InitChessProgram(cps, setup)
9387      ChessProgramState *cps;
9388      int setup; /* [HGM] needed to setup FRC opening position */
9389 {
9390     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9391     if (appData.noChessProgram) return;
9392     hintRequested = FALSE;
9393     bookRequested = FALSE;
9394
9395     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9396     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9397     if(cps->memSize) { /* [HGM] memory */
9398       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9399         SendToProgram(buf, cps);
9400     }
9401     SendEgtPath(cps); /* [HGM] EGT */
9402     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9403       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9404         SendToProgram(buf, cps);
9405     }
9406
9407     SendToProgram(cps->initString, cps);
9408     if (gameInfo.variant != VariantNormal &&
9409         gameInfo.variant != VariantLoadable
9410         /* [HGM] also send variant if board size non-standard */
9411         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9412                                             ) {
9413       char *v = VariantName(gameInfo.variant);
9414       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9415         /* [HGM] in protocol 1 we have to assume all variants valid */
9416         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9417         DisplayFatalError(buf, 0, 1);
9418         return;
9419       }
9420
9421       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9422       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9423       if( gameInfo.variant == VariantXiangqi )
9424            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9425       if( gameInfo.variant == VariantShogi )
9426            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9427       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9428            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9429       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9430           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9431            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9432       if( gameInfo.variant == VariantCourier )
9433            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9434       if( gameInfo.variant == VariantSuper )
9435            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9436       if( gameInfo.variant == VariantGreat )
9437            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9438       if( gameInfo.variant == VariantSChess )
9439            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9440
9441       if(overruled) {
9442         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9443                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9444            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9445            if(StrStr(cps->variants, b) == NULL) {
9446                // specific sized variant not known, check if general sizing allowed
9447                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9448                    if(StrStr(cps->variants, "boardsize") == NULL) {
9449                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9450                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9451                        DisplayFatalError(buf, 0, 1);
9452                        return;
9453                    }
9454                    /* [HGM] here we really should compare with the maximum supported board size */
9455                }
9456            }
9457       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9458       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9459       SendToProgram(buf, cps);
9460     }
9461     currentlyInitializedVariant = gameInfo.variant;
9462
9463     /* [HGM] send opening position in FRC to first engine */
9464     if(setup) {
9465           SendToProgram("force\n", cps);
9466           SendBoard(cps, 0);
9467           /* engine is now in force mode! Set flag to wake it up after first move. */
9468           setboardSpoiledMachineBlack = 1;
9469     }
9470
9471     if (cps->sendICS) {
9472       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9473       SendToProgram(buf, cps);
9474     }
9475     cps->maybeThinking = FALSE;
9476     cps->offeredDraw = 0;
9477     if (!appData.icsActive) {
9478         SendTimeControl(cps, movesPerSession, timeControl,
9479                         timeIncrement, appData.searchDepth,
9480                         searchTime);
9481     }
9482     if (appData.showThinking
9483         // [HGM] thinking: four options require thinking output to be sent
9484         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9485                                 ) {
9486         SendToProgram("post\n", cps);
9487     }
9488     SendToProgram("hard\n", cps);
9489     if (!appData.ponderNextMove) {
9490         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9491            it without being sure what state we are in first.  "hard"
9492            is not a toggle, so that one is OK.
9493          */
9494         SendToProgram("easy\n", cps);
9495     }
9496     if (cps->usePing) {
9497       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9498       SendToProgram(buf, cps);
9499     }
9500     cps->initDone = TRUE;
9501     ClearEngineOutputPane(cps == &second);
9502 }
9503
9504
9505 void
9506 StartChessProgram(cps)
9507      ChessProgramState *cps;
9508 {
9509     char buf[MSG_SIZ];
9510     int err;
9511
9512     if (appData.noChessProgram) return;
9513     cps->initDone = FALSE;
9514
9515     if (strcmp(cps->host, "localhost") == 0) {
9516         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9517     } else if (*appData.remoteShell == NULLCHAR) {
9518         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9519     } else {
9520         if (*appData.remoteUser == NULLCHAR) {
9521           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9522                     cps->program);
9523         } else {
9524           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9525                     cps->host, appData.remoteUser, cps->program);
9526         }
9527         err = StartChildProcess(buf, "", &cps->pr);
9528     }
9529
9530     if (err != 0) {
9531       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9532         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9533         if(cps != &first) return;
9534         appData.noChessProgram = TRUE;
9535         ThawUI();
9536         SetNCPMode();
9537 //      DisplayFatalError(buf, err, 1);
9538 //      cps->pr = NoProc;
9539 //      cps->isr = NULL;
9540         return;
9541     }
9542
9543     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9544     if (cps->protocolVersion > 1) {
9545       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9546       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9547       cps->comboCnt = 0;  //                and values of combo boxes
9548       SendToProgram(buf, cps);
9549     } else {
9550       SendToProgram("xboard\n", cps);
9551     }
9552 }
9553
9554 void
9555 TwoMachinesEventIfReady P((void))
9556 {
9557   static int curMess = 0;
9558   if (first.lastPing != first.lastPong) {
9559     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9560     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9561     return;
9562   }
9563   if (second.lastPing != second.lastPong) {
9564     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9565     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9566     return;
9567   }
9568   DisplayMessage("", ""); curMess = 0;
9569   ThawUI();
9570   TwoMachinesEvent();
9571 }
9572
9573 char *
9574 MakeName(char *template)
9575 {
9576     time_t clock;
9577     struct tm *tm;
9578     static char buf[MSG_SIZ];
9579     char *p = buf;
9580     int i;
9581
9582     clock = time((time_t *)NULL);
9583     tm = localtime(&clock);
9584
9585     while(*p++ = *template++) if(p[-1] == '%') {
9586         switch(*template++) {
9587           case 0:   *p = 0; return buf;
9588           case 'Y': i = tm->tm_year+1900; break;
9589           case 'y': i = tm->tm_year-100; break;
9590           case 'M': i = tm->tm_mon+1; break;
9591           case 'd': i = tm->tm_mday; break;
9592           case 'h': i = tm->tm_hour; break;
9593           case 'm': i = tm->tm_min; break;
9594           case 's': i = tm->tm_sec; break;
9595           default:  i = 0;
9596         }
9597         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9598     }
9599     return buf;
9600 }
9601
9602 int
9603 CountPlayers(char *p)
9604 {
9605     int n = 0;
9606     while(p = strchr(p, '\n')) p++, n++; // count participants
9607     return n;
9608 }
9609
9610 FILE *
9611 WriteTourneyFile(char *results)
9612 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9613     FILE *f = fopen(appData.tourneyFile, "w");
9614     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9615         // create a file with tournament description
9616         fprintf(f, "-participants {%s}\n", appData.participants);
9617         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9618         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9619         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9620         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9621         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9622         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9623         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9624         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9625         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9626         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9627         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9628         if(searchTime > 0)
9629                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9630         else {
9631                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9632                 fprintf(f, "-tc %s\n", appData.timeControl);
9633                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9634         }
9635         fprintf(f, "-results \"%s\"\n", results);
9636     }
9637     return f;
9638 }
9639
9640 int
9641 CreateTourney(char *name)
9642 {
9643         FILE *f;
9644         if(name[0] == NULLCHAR) {
9645             if(appData.participants[0])
9646                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9647             return 0;
9648         }
9649         f = fopen(name, "r");
9650         if(f) { // file exists
9651             ASSIGN(appData.tourneyFile, name);
9652             ParseArgsFromFile(f); // parse it
9653         } else {
9654             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9655             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9656                 DisplayError(_("Not enough participants"), 0);
9657                 return 0;
9658             }
9659             ASSIGN(appData.tourneyFile, name);
9660             if((f = WriteTourneyFile("")) == NULL) return 0;
9661         }
9662         fclose(f);
9663         appData.noChessProgram = FALSE;
9664         appData.clockMode = TRUE;
9665         SetGNUMode();
9666         return 1;
9667 }
9668
9669 #define MAXENGINES 1000
9670 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9671
9672 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9673 {
9674     char buf[MSG_SIZ], *p, *q;
9675     int i=1;
9676     while(*names) {
9677         p = names; q = buf;
9678         while(*p && *p != '\n') *q++ = *p++;
9679         *q = 0;
9680         if(engineList[i]) free(engineList[i]);
9681         engineList[i] = strdup(buf);
9682         if(*p == '\n') p++;
9683         TidyProgramName(engineList[i], "localhost", buf);
9684         if(engineMnemonic[i]) free(engineMnemonic[i]);
9685         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9686             strcat(buf, " (");
9687             sscanf(q + 8, "%s", buf + strlen(buf));
9688             strcat(buf, ")");
9689         }
9690         engineMnemonic[i] = strdup(buf);
9691         names = p; i++;
9692       if(i > MAXENGINES - 2) break;
9693     }
9694     engineList[i] = NULL;
9695 }
9696
9697 // following implemented as macro to avoid type limitations
9698 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9699
9700 void SwapEngines(int n)
9701 {   // swap settings for first engine and other engine (so far only some selected options)
9702     int h;
9703     char *p;
9704     if(n == 0) return;
9705     SWAP(directory, p)
9706     SWAP(chessProgram, p)
9707     SWAP(isUCI, h)
9708     SWAP(hasOwnBookUCI, h)
9709     SWAP(protocolVersion, h)
9710     SWAP(reuse, h)
9711     SWAP(scoreIsAbsolute, h)
9712     SWAP(timeOdds, h)
9713     SWAP(logo, p)
9714     SWAP(pgnName, p)
9715 }
9716
9717 void
9718 SetPlayer(int player)
9719 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9720     int i;
9721     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9722     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9723     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9724     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9725     if(mnemonic[i]) {
9726         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9727         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9728         ParseArgsFromString(buf);
9729     }
9730     free(engineName);
9731 }
9732
9733 int
9734 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9735 {   // determine players from game number
9736     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9737
9738     if(appData.tourneyType == 0) {
9739         roundsPerCycle = (nPlayers - 1) | 1;
9740         pairingsPerRound = nPlayers / 2;
9741     } else if(appData.tourneyType > 0) {
9742         roundsPerCycle = nPlayers - appData.tourneyType;
9743         pairingsPerRound = appData.tourneyType;
9744     }
9745     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9746     gamesPerCycle = gamesPerRound * roundsPerCycle;
9747     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9748     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9749     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9750     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9751     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9752     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9753
9754     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9755     if(appData.roundSync) *syncInterval = gamesPerRound;
9756
9757     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9758
9759     if(appData.tourneyType == 0) {
9760         if(curPairing == (nPlayers-1)/2 ) {
9761             *whitePlayer = curRound;
9762             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9763         } else {
9764             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9765             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9766             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9767             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9768         }
9769     } else if(appData.tourneyType > 0) {
9770         *whitePlayer = curPairing;
9771         *blackPlayer = curRound + appData.tourneyType;
9772     }
9773
9774     // take care of white/black alternation per round. 
9775     // For cycles and games this is already taken care of by default, derived from matchGame!
9776     return curRound & 1;
9777 }
9778
9779 int
9780 NextTourneyGame(int nr, int *swapColors)
9781 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9782     char *p, *q;
9783     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9784     FILE *tf;
9785     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9786     tf = fopen(appData.tourneyFile, "r");
9787     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9788     ParseArgsFromFile(tf); fclose(tf);
9789     InitTimeControls(); // TC might be altered from tourney file
9790
9791     nPlayers = CountPlayers(appData.participants); // count participants
9792     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9793     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9794
9795     if(syncInterval) {
9796         p = q = appData.results;
9797         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9798         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9799             DisplayMessage(_("Waiting for other game(s)"),"");
9800             waitingForGame = TRUE;
9801             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9802             return 0;
9803         }
9804         waitingForGame = FALSE;
9805     }
9806
9807     if(appData.tourneyType < 0) {
9808         if(nr>=0 && !pairingReceived) {
9809             char buf[1<<16];
9810             if(pairing.pr == NoProc) {
9811                 if(!appData.pairingEngine[0]) {
9812                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9813                     return 0;
9814                 }
9815                 StartChessProgram(&pairing); // starts the pairing engine
9816             }
9817             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9818             SendToProgram(buf, &pairing);
9819             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9820             SendToProgram(buf, &pairing);
9821             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9822         }
9823         pairingReceived = 0;                              // ... so we continue here 
9824         *swapColors = 0;
9825         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9826         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9827         matchGame = 1; roundNr = nr / syncInterval + 1;
9828     }
9829
9830     if(first.pr != NoProc) return 1; // engines already loaded
9831
9832     // redefine engines, engine dir, etc.
9833     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9834     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9835     SwapEngines(1);
9836     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9837     SwapEngines(1);         // and make that valid for second engine by swapping
9838     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9839     InitEngine(&second, 1);
9840     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9841     return 1;
9842 }
9843
9844 void
9845 NextMatchGame()
9846 {   // performs game initialization that does not invoke engines, and then tries to start the game
9847     int firstWhite, swapColors = 0;
9848     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9849     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9850     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9851     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9852     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9853     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9854     Reset(FALSE, first.pr != NoProc);
9855     appData.noChessProgram = FALSE;
9856     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9857     TwoMachinesEvent();
9858 }
9859
9860 void UserAdjudicationEvent( int result )
9861 {
9862     ChessMove gameResult = GameIsDrawn;
9863
9864     if( result > 0 ) {
9865         gameResult = WhiteWins;
9866     }
9867     else if( result < 0 ) {
9868         gameResult = BlackWins;
9869     }
9870
9871     if( gameMode == TwoMachinesPlay ) {
9872         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9873     }
9874 }
9875
9876
9877 // [HGM] save: calculate checksum of game to make games easily identifiable
9878 int StringCheckSum(char *s)
9879 {
9880         int i = 0;
9881         if(s==NULL) return 0;
9882         while(*s) i = i*259 + *s++;
9883         return i;
9884 }
9885
9886 int GameCheckSum()
9887 {
9888         int i, sum=0;
9889         for(i=backwardMostMove; i<forwardMostMove; i++) {
9890                 sum += pvInfoList[i].depth;
9891                 sum += StringCheckSum(parseList[i]);
9892                 sum += StringCheckSum(commentList[i]);
9893                 sum *= 261;
9894         }
9895         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9896         return sum + StringCheckSum(commentList[i]);
9897 } // end of save patch
9898
9899 void
9900 GameEnds(result, resultDetails, whosays)
9901      ChessMove result;
9902      char *resultDetails;
9903      int whosays;
9904 {
9905     GameMode nextGameMode;
9906     int isIcsGame;
9907     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9908
9909     if(endingGame) return; /* [HGM] crash: forbid recursion */
9910     endingGame = 1;
9911     if(twoBoards) { // [HGM] dual: switch back to one board
9912         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9913         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9914     }
9915     if (appData.debugMode) {
9916       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9917               result, resultDetails ? resultDetails : "(null)", whosays);
9918     }
9919
9920     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9921
9922     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9923         /* If we are playing on ICS, the server decides when the
9924            game is over, but the engine can offer to draw, claim
9925            a draw, or resign.
9926          */
9927 #if ZIPPY
9928         if (appData.zippyPlay && first.initDone) {
9929             if (result == GameIsDrawn) {
9930                 /* In case draw still needs to be claimed */
9931                 SendToICS(ics_prefix);
9932                 SendToICS("draw\n");
9933             } else if (StrCaseStr(resultDetails, "resign")) {
9934                 SendToICS(ics_prefix);
9935                 SendToICS("resign\n");
9936             }
9937         }
9938 #endif
9939         endingGame = 0; /* [HGM] crash */
9940         return;
9941     }
9942
9943     /* If we're loading the game from a file, stop */
9944     if (whosays == GE_FILE) {
9945       (void) StopLoadGameTimer();
9946       gameFileFP = NULL;
9947     }
9948
9949     /* Cancel draw offers */
9950     first.offeredDraw = second.offeredDraw = 0;
9951
9952     /* If this is an ICS game, only ICS can really say it's done;
9953        if not, anyone can. */
9954     isIcsGame = (gameMode == IcsPlayingWhite ||
9955                  gameMode == IcsPlayingBlack ||
9956                  gameMode == IcsObserving    ||
9957                  gameMode == IcsExamining);
9958
9959     if (!isIcsGame || whosays == GE_ICS) {
9960         /* OK -- not an ICS game, or ICS said it was done */
9961         StopClocks();
9962         if (!isIcsGame && !appData.noChessProgram)
9963           SetUserThinkingEnables();
9964
9965         /* [HGM] if a machine claims the game end we verify this claim */
9966         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9967             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9968                 char claimer;
9969                 ChessMove trueResult = (ChessMove) -1;
9970
9971                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9972                                             first.twoMachinesColor[0] :
9973                                             second.twoMachinesColor[0] ;
9974
9975                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9976                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9977                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9978                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9979                 } else
9980                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9981                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9982                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9983                 } else
9984                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9985                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9986                 }
9987
9988                 // now verify win claims, but not in drop games, as we don't understand those yet
9989                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9990                                                  || gameInfo.variant == VariantGreat) &&
9991                     (result == WhiteWins && claimer == 'w' ||
9992                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9993                       if (appData.debugMode) {
9994                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9995                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9996                       }
9997                       if(result != trueResult) {
9998                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9999                               result = claimer == 'w' ? BlackWins : WhiteWins;
10000                               resultDetails = buf;
10001                       }
10002                 } else
10003                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10004                     && (forwardMostMove <= backwardMostMove ||
10005                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10006                         (claimer=='b')==(forwardMostMove&1))
10007                                                                                   ) {
10008                       /* [HGM] verify: draws that were not flagged are false claims */
10009                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10010                       result = claimer == 'w' ? BlackWins : WhiteWins;
10011                       resultDetails = buf;
10012                 }
10013                 /* (Claiming a loss is accepted no questions asked!) */
10014             }
10015             /* [HGM] bare: don't allow bare King to win */
10016             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10017                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10018                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10019                && result != GameIsDrawn)
10020             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10021                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10022                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10023                         if(p >= 0 && p <= (int)WhiteKing) k++;
10024                 }
10025                 if (appData.debugMode) {
10026                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10027                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10028                 }
10029                 if(k <= 1) {
10030                         result = GameIsDrawn;
10031                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10032                         resultDetails = buf;
10033                 }
10034             }
10035         }
10036
10037
10038         if(serverMoves != NULL && !loadFlag) { char c = '=';
10039             if(result==WhiteWins) c = '+';
10040             if(result==BlackWins) c = '-';
10041             if(resultDetails != NULL)
10042                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10043         }
10044         if (resultDetails != NULL) {
10045             gameInfo.result = result;
10046             gameInfo.resultDetails = StrSave(resultDetails);
10047
10048             /* display last move only if game was not loaded from file */
10049             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10050                 DisplayMove(currentMove - 1);
10051
10052             if (forwardMostMove != 0) {
10053                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10054                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10055                                                                 ) {
10056                     if (*appData.saveGameFile != NULLCHAR) {
10057                         SaveGameToFile(appData.saveGameFile, TRUE);
10058                     } else if (appData.autoSaveGames) {
10059                         AutoSaveGame();
10060                     }
10061                     if (*appData.savePositionFile != NULLCHAR) {
10062                         SavePositionToFile(appData.savePositionFile);
10063                     }
10064                 }
10065             }
10066
10067             /* Tell program how game ended in case it is learning */
10068             /* [HGM] Moved this to after saving the PGN, just in case */
10069             /* engine died and we got here through time loss. In that */
10070             /* case we will get a fatal error writing the pipe, which */
10071             /* would otherwise lose us the PGN.                       */
10072             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10073             /* output during GameEnds should never be fatal anymore   */
10074             if (gameMode == MachinePlaysWhite ||
10075                 gameMode == MachinePlaysBlack ||
10076                 gameMode == TwoMachinesPlay ||
10077                 gameMode == IcsPlayingWhite ||
10078                 gameMode == IcsPlayingBlack ||
10079                 gameMode == BeginningOfGame) {
10080                 char buf[MSG_SIZ];
10081                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10082                         resultDetails);
10083                 if (first.pr != NoProc) {
10084                     SendToProgram(buf, &first);
10085                 }
10086                 if (second.pr != NoProc &&
10087                     gameMode == TwoMachinesPlay) {
10088                     SendToProgram(buf, &second);
10089                 }
10090             }
10091         }
10092
10093         if (appData.icsActive) {
10094             if (appData.quietPlay &&
10095                 (gameMode == IcsPlayingWhite ||
10096                  gameMode == IcsPlayingBlack)) {
10097                 SendToICS(ics_prefix);
10098                 SendToICS("set shout 1\n");
10099             }
10100             nextGameMode = IcsIdle;
10101             ics_user_moved = FALSE;
10102             /* clean up premove.  It's ugly when the game has ended and the
10103              * premove highlights are still on the board.
10104              */
10105             if (gotPremove) {
10106               gotPremove = FALSE;
10107               ClearPremoveHighlights();
10108               DrawPosition(FALSE, boards[currentMove]);
10109             }
10110             if (whosays == GE_ICS) {
10111                 switch (result) {
10112                 case WhiteWins:
10113                     if (gameMode == IcsPlayingWhite)
10114                         PlayIcsWinSound();
10115                     else if(gameMode == IcsPlayingBlack)
10116                         PlayIcsLossSound();
10117                     break;
10118                 case BlackWins:
10119                     if (gameMode == IcsPlayingBlack)
10120                         PlayIcsWinSound();
10121                     else if(gameMode == IcsPlayingWhite)
10122                         PlayIcsLossSound();
10123                     break;
10124                 case GameIsDrawn:
10125                     PlayIcsDrawSound();
10126                     break;
10127                 default:
10128                     PlayIcsUnfinishedSound();
10129                 }
10130             }
10131         } else if (gameMode == EditGame ||
10132                    gameMode == PlayFromGameFile ||
10133                    gameMode == AnalyzeMode ||
10134                    gameMode == AnalyzeFile) {
10135             nextGameMode = gameMode;
10136         } else {
10137             nextGameMode = EndOfGame;
10138         }
10139         pausing = FALSE;
10140         ModeHighlight();
10141     } else {
10142         nextGameMode = gameMode;
10143     }
10144
10145     if (appData.noChessProgram) {
10146         gameMode = nextGameMode;
10147         ModeHighlight();
10148         endingGame = 0; /* [HGM] crash */
10149         return;
10150     }
10151
10152     if (first.reuse) {
10153         /* Put first chess program into idle state */
10154         if (first.pr != NoProc &&
10155             (gameMode == MachinePlaysWhite ||
10156              gameMode == MachinePlaysBlack ||
10157              gameMode == TwoMachinesPlay ||
10158              gameMode == IcsPlayingWhite ||
10159              gameMode == IcsPlayingBlack ||
10160              gameMode == BeginningOfGame)) {
10161             SendToProgram("force\n", &first);
10162             if (first.usePing) {
10163               char buf[MSG_SIZ];
10164               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10165               SendToProgram(buf, &first);
10166             }
10167         }
10168     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10169         /* Kill off first chess program */
10170         if (first.isr != NULL)
10171           RemoveInputSource(first.isr);
10172         first.isr = NULL;
10173
10174         if (first.pr != NoProc) {
10175             ExitAnalyzeMode();
10176             DoSleep( appData.delayBeforeQuit );
10177             SendToProgram("quit\n", &first);
10178             DoSleep( appData.delayAfterQuit );
10179             DestroyChildProcess(first.pr, first.useSigterm);
10180         }
10181         first.pr = NoProc;
10182     }
10183     if (second.reuse) {
10184         /* Put second chess program into idle state */
10185         if (second.pr != NoProc &&
10186             gameMode == TwoMachinesPlay) {
10187             SendToProgram("force\n", &second);
10188             if (second.usePing) {
10189               char buf[MSG_SIZ];
10190               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10191               SendToProgram(buf, &second);
10192             }
10193         }
10194     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10195         /* Kill off second chess program */
10196         if (second.isr != NULL)
10197           RemoveInputSource(second.isr);
10198         second.isr = NULL;
10199
10200         if (second.pr != NoProc) {
10201             DoSleep( appData.delayBeforeQuit );
10202             SendToProgram("quit\n", &second);
10203             DoSleep( appData.delayAfterQuit );
10204             DestroyChildProcess(second.pr, second.useSigterm);
10205         }
10206         second.pr = NoProc;
10207     }
10208
10209     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10210         char resChar = '=';
10211         switch (result) {
10212         case WhiteWins:
10213           resChar = '+';
10214           if (first.twoMachinesColor[0] == 'w') {
10215             first.matchWins++;
10216           } else {
10217             second.matchWins++;
10218           }
10219           break;
10220         case BlackWins:
10221           resChar = '-';
10222           if (first.twoMachinesColor[0] == 'b') {
10223             first.matchWins++;
10224           } else {
10225             second.matchWins++;
10226           }
10227           break;
10228         case GameUnfinished:
10229           resChar = ' ';
10230         default:
10231           break;
10232         }
10233
10234         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10235         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10236             ReserveGame(nextGame, resChar); // sets nextGame
10237             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10238             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10239         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10240
10241         if (nextGame <= appData.matchGames && !abortMatch) {
10242             gameMode = nextGameMode;
10243             matchGame = nextGame; // this will be overruled in tourney mode!
10244             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10245             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10246             endingGame = 0; /* [HGM] crash */
10247             return;
10248         } else {
10249             gameMode = nextGameMode;
10250             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10251                      first.tidy, second.tidy,
10252                      first.matchWins, second.matchWins,
10253                      appData.matchGames - (first.matchWins + second.matchWins));
10254             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10255             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10256                 first.twoMachinesColor = "black\n";
10257                 second.twoMachinesColor = "white\n";
10258             } else {
10259                 first.twoMachinesColor = "white\n";
10260                 second.twoMachinesColor = "black\n";
10261             }
10262         }
10263     }
10264     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10265         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10266       ExitAnalyzeMode();
10267     gameMode = nextGameMode;
10268     ModeHighlight();
10269     endingGame = 0;  /* [HGM] crash */
10270     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10271         if(matchMode == TRUE) { // match through command line: exit with or without popup
10272             if(ranking) {
10273                 ToNrEvent(forwardMostMove);
10274                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10275                 else ExitEvent(0);
10276             } else DisplayFatalError(buf, 0, 0);
10277         } else { // match through menu; just stop, with or without popup
10278             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10279             if(ranking){
10280                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10281             } else DisplayNote(buf);
10282       }
10283       if(ranking) free(ranking);
10284     }
10285 }
10286
10287 /* Assumes program was just initialized (initString sent).
10288    Leaves program in force mode. */
10289 void
10290 FeedMovesToProgram(cps, upto)
10291      ChessProgramState *cps;
10292      int upto;
10293 {
10294     int i;
10295
10296     if (appData.debugMode)
10297       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10298               startedFromSetupPosition ? "position and " : "",
10299               backwardMostMove, upto, cps->which);
10300     if(currentlyInitializedVariant != gameInfo.variant) {
10301       char buf[MSG_SIZ];
10302         // [HGM] variantswitch: make engine aware of new variant
10303         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10304                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10305         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10306         SendToProgram(buf, cps);
10307         currentlyInitializedVariant = gameInfo.variant;
10308     }
10309     SendToProgram("force\n", cps);
10310     if (startedFromSetupPosition) {
10311         SendBoard(cps, backwardMostMove);
10312     if (appData.debugMode) {
10313         fprintf(debugFP, "feedMoves\n");
10314     }
10315     }
10316     for (i = backwardMostMove; i < upto; i++) {
10317         SendMoveToProgram(i, cps);
10318     }
10319 }
10320
10321
10322 int
10323 ResurrectChessProgram()
10324 {
10325      /* The chess program may have exited.
10326         If so, restart it and feed it all the moves made so far. */
10327     static int doInit = 0;
10328
10329     if (appData.noChessProgram) return 1;
10330
10331     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10332         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10333         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10334         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10335     } else {
10336         if (first.pr != NoProc) return 1;
10337         StartChessProgram(&first);
10338     }
10339     InitChessProgram(&first, FALSE);
10340     FeedMovesToProgram(&first, currentMove);
10341
10342     if (!first.sendTime) {
10343         /* can't tell gnuchess what its clock should read,
10344            so we bow to its notion. */
10345         ResetClocks();
10346         timeRemaining[0][currentMove] = whiteTimeRemaining;
10347         timeRemaining[1][currentMove] = blackTimeRemaining;
10348     }
10349
10350     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10351                 appData.icsEngineAnalyze) && first.analysisSupport) {
10352       SendToProgram("analyze\n", &first);
10353       first.analyzing = TRUE;
10354     }
10355     return 1;
10356 }
10357
10358 /*
10359  * Button procedures
10360  */
10361 void
10362 Reset(redraw, init)
10363      int redraw, init;
10364 {
10365     int i;
10366
10367     if (appData.debugMode) {
10368         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10369                 redraw, init, gameMode);
10370     }
10371     CleanupTail(); // [HGM] vari: delete any stored variations
10372     pausing = pauseExamInvalid = FALSE;
10373     startedFromSetupPosition = blackPlaysFirst = FALSE;
10374     firstMove = TRUE;
10375     whiteFlag = blackFlag = FALSE;
10376     userOfferedDraw = FALSE;
10377     hintRequested = bookRequested = FALSE;
10378     first.maybeThinking = FALSE;
10379     second.maybeThinking = FALSE;
10380     first.bookSuspend = FALSE; // [HGM] book
10381     second.bookSuspend = FALSE;
10382     thinkOutput[0] = NULLCHAR;
10383     lastHint[0] = NULLCHAR;
10384     ClearGameInfo(&gameInfo);
10385     gameInfo.variant = StringToVariant(appData.variant);
10386     ics_user_moved = ics_clock_paused = FALSE;
10387     ics_getting_history = H_FALSE;
10388     ics_gamenum = -1;
10389     white_holding[0] = black_holding[0] = NULLCHAR;
10390     ClearProgramStats();
10391     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10392
10393     ResetFrontEnd();
10394     ClearHighlights();
10395     flipView = appData.flipView;
10396     ClearPremoveHighlights();
10397     gotPremove = FALSE;
10398     alarmSounded = FALSE;
10399
10400     GameEnds(EndOfFile, NULL, GE_PLAYER);
10401     if(appData.serverMovesName != NULL) {
10402         /* [HGM] prepare to make moves file for broadcasting */
10403         clock_t t = clock();
10404         if(serverMoves != NULL) fclose(serverMoves);
10405         serverMoves = fopen(appData.serverMovesName, "r");
10406         if(serverMoves != NULL) {
10407             fclose(serverMoves);
10408             /* delay 15 sec before overwriting, so all clients can see end */
10409             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10410         }
10411         serverMoves = fopen(appData.serverMovesName, "w");
10412     }
10413
10414     ExitAnalyzeMode();
10415     gameMode = BeginningOfGame;
10416     ModeHighlight();
10417     if(appData.icsActive) gameInfo.variant = VariantNormal;
10418     currentMove = forwardMostMove = backwardMostMove = 0;
10419     InitPosition(redraw);
10420     for (i = 0; i < MAX_MOVES; i++) {
10421         if (commentList[i] != NULL) {
10422             free(commentList[i]);
10423             commentList[i] = NULL;
10424         }
10425     }
10426     ResetClocks();
10427     timeRemaining[0][0] = whiteTimeRemaining;
10428     timeRemaining[1][0] = blackTimeRemaining;
10429
10430     if (first.pr == NULL) {
10431         StartChessProgram(&first);
10432     }
10433     if (init) {
10434             InitChessProgram(&first, startedFromSetupPosition);
10435     }
10436     DisplayTitle("");
10437     DisplayMessage("", "");
10438     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10439     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10440 }
10441
10442 void
10443 AutoPlayGameLoop()
10444 {
10445     for (;;) {
10446         if (!AutoPlayOneMove())
10447           return;
10448         if (matchMode || appData.timeDelay == 0)
10449           continue;
10450         if (appData.timeDelay < 0)
10451           return;
10452         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10453         break;
10454     }
10455 }
10456
10457
10458 int
10459 AutoPlayOneMove()
10460 {
10461     int fromX, fromY, toX, toY;
10462
10463     if (appData.debugMode) {
10464       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10465     }
10466
10467     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10468       return FALSE;
10469
10470     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10471       pvInfoList[currentMove].depth = programStats.depth;
10472       pvInfoList[currentMove].score = programStats.score;
10473       pvInfoList[currentMove].time  = 0;
10474       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10475     }
10476
10477     if (currentMove >= forwardMostMove) {
10478       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10479       gameMode = EditGame;
10480       ModeHighlight();
10481
10482       /* [AS] Clear current move marker at the end of a game */
10483       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10484
10485       return FALSE;
10486     }
10487
10488     toX = moveList[currentMove][2] - AAA;
10489     toY = moveList[currentMove][3] - ONE;
10490
10491     if (moveList[currentMove][1] == '@') {
10492         if (appData.highlightLastMove) {
10493             SetHighlights(-1, -1, toX, toY);
10494         }
10495     } else {
10496         fromX = moveList[currentMove][0] - AAA;
10497         fromY = moveList[currentMove][1] - ONE;
10498
10499         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10500
10501         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10502
10503         if (appData.highlightLastMove) {
10504             SetHighlights(fromX, fromY, toX, toY);
10505         }
10506     }
10507     DisplayMove(currentMove);
10508     SendMoveToProgram(currentMove++, &first);
10509     DisplayBothClocks();
10510     DrawPosition(FALSE, boards[currentMove]);
10511     // [HGM] PV info: always display, routine tests if empty
10512     DisplayComment(currentMove - 1, commentList[currentMove]);
10513     return TRUE;
10514 }
10515
10516
10517 int
10518 LoadGameOneMove(readAhead)
10519      ChessMove readAhead;
10520 {
10521     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10522     char promoChar = NULLCHAR;
10523     ChessMove moveType;
10524     char move[MSG_SIZ];
10525     char *p, *q;
10526
10527     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10528         gameMode != AnalyzeMode && gameMode != Training) {
10529         gameFileFP = NULL;
10530         return FALSE;
10531     }
10532
10533     yyboardindex = forwardMostMove;
10534     if (readAhead != EndOfFile) {
10535       moveType = readAhead;
10536     } else {
10537       if (gameFileFP == NULL)
10538           return FALSE;
10539       moveType = (ChessMove) Myylex();
10540     }
10541
10542     done = FALSE;
10543     switch (moveType) {
10544       case Comment:
10545         if (appData.debugMode)
10546           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10547         p = yy_text;
10548
10549         /* append the comment but don't display it */
10550         AppendComment(currentMove, p, FALSE);
10551         return TRUE;
10552
10553       case WhiteCapturesEnPassant:
10554       case BlackCapturesEnPassant:
10555       case WhitePromotion:
10556       case BlackPromotion:
10557       case WhiteNonPromotion:
10558       case BlackNonPromotion:
10559       case NormalMove:
10560       case WhiteKingSideCastle:
10561       case WhiteQueenSideCastle:
10562       case BlackKingSideCastle:
10563       case BlackQueenSideCastle:
10564       case WhiteKingSideCastleWild:
10565       case WhiteQueenSideCastleWild:
10566       case BlackKingSideCastleWild:
10567       case BlackQueenSideCastleWild:
10568       /* PUSH Fabien */
10569       case WhiteHSideCastleFR:
10570       case WhiteASideCastleFR:
10571       case BlackHSideCastleFR:
10572       case BlackASideCastleFR:
10573       /* POP Fabien */
10574         if (appData.debugMode)
10575           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10576         fromX = currentMoveString[0] - AAA;
10577         fromY = currentMoveString[1] - ONE;
10578         toX = currentMoveString[2] - AAA;
10579         toY = currentMoveString[3] - ONE;
10580         promoChar = currentMoveString[4];
10581         break;
10582
10583       case WhiteDrop:
10584       case BlackDrop:
10585         if (appData.debugMode)
10586           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10587         fromX = moveType == WhiteDrop ?
10588           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10589         (int) CharToPiece(ToLower(currentMoveString[0]));
10590         fromY = DROP_RANK;
10591         toX = currentMoveString[2] - AAA;
10592         toY = currentMoveString[3] - ONE;
10593         break;
10594
10595       case WhiteWins:
10596       case BlackWins:
10597       case GameIsDrawn:
10598       case GameUnfinished:
10599         if (appData.debugMode)
10600           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10601         p = strchr(yy_text, '{');
10602         if (p == NULL) p = strchr(yy_text, '(');
10603         if (p == NULL) {
10604             p = yy_text;
10605             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10606         } else {
10607             q = strchr(p, *p == '{' ? '}' : ')');
10608             if (q != NULL) *q = NULLCHAR;
10609             p++;
10610         }
10611         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10612         GameEnds(moveType, p, GE_FILE);
10613         done = TRUE;
10614         if (cmailMsgLoaded) {
10615             ClearHighlights();
10616             flipView = WhiteOnMove(currentMove);
10617             if (moveType == GameUnfinished) flipView = !flipView;
10618             if (appData.debugMode)
10619               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10620         }
10621         break;
10622
10623       case EndOfFile:
10624         if (appData.debugMode)
10625           fprintf(debugFP, "Parser hit end of file\n");
10626         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10627           case MT_NONE:
10628           case MT_CHECK:
10629             break;
10630           case MT_CHECKMATE:
10631           case MT_STAINMATE:
10632             if (WhiteOnMove(currentMove)) {
10633                 GameEnds(BlackWins, "Black mates", GE_FILE);
10634             } else {
10635                 GameEnds(WhiteWins, "White mates", GE_FILE);
10636             }
10637             break;
10638           case MT_STALEMATE:
10639             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10640             break;
10641         }
10642         done = TRUE;
10643         break;
10644
10645       case MoveNumberOne:
10646         if (lastLoadGameStart == GNUChessGame) {
10647             /* GNUChessGames have numbers, but they aren't move numbers */
10648             if (appData.debugMode)
10649               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10650                       yy_text, (int) moveType);
10651             return LoadGameOneMove(EndOfFile); /* tail recursion */
10652         }
10653         /* else fall thru */
10654
10655       case XBoardGame:
10656       case GNUChessGame:
10657       case PGNTag:
10658         /* Reached start of next game in file */
10659         if (appData.debugMode)
10660           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10661         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10662           case MT_NONE:
10663           case MT_CHECK:
10664             break;
10665           case MT_CHECKMATE:
10666           case MT_STAINMATE:
10667             if (WhiteOnMove(currentMove)) {
10668                 GameEnds(BlackWins, "Black mates", GE_FILE);
10669             } else {
10670                 GameEnds(WhiteWins, "White mates", GE_FILE);
10671             }
10672             break;
10673           case MT_STALEMATE:
10674             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10675             break;
10676         }
10677         done = TRUE;
10678         break;
10679
10680       case PositionDiagram:     /* should not happen; ignore */
10681       case ElapsedTime:         /* ignore */
10682       case NAG:                 /* ignore */
10683         if (appData.debugMode)
10684           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10685                   yy_text, (int) moveType);
10686         return LoadGameOneMove(EndOfFile); /* tail recursion */
10687
10688       case IllegalMove:
10689         if (appData.testLegality) {
10690             if (appData.debugMode)
10691               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10692             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10693                     (forwardMostMove / 2) + 1,
10694                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10695             DisplayError(move, 0);
10696             done = TRUE;
10697         } else {
10698             if (appData.debugMode)
10699               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10700                       yy_text, currentMoveString);
10701             fromX = currentMoveString[0] - AAA;
10702             fromY = currentMoveString[1] - ONE;
10703             toX = currentMoveString[2] - AAA;
10704             toY = currentMoveString[3] - ONE;
10705             promoChar = currentMoveString[4];
10706         }
10707         break;
10708
10709       case AmbiguousMove:
10710         if (appData.debugMode)
10711           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10712         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10713                 (forwardMostMove / 2) + 1,
10714                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10715         DisplayError(move, 0);
10716         done = TRUE;
10717         break;
10718
10719       default:
10720       case ImpossibleMove:
10721         if (appData.debugMode)
10722           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10723         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10724                 (forwardMostMove / 2) + 1,
10725                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10726         DisplayError(move, 0);
10727         done = TRUE;
10728         break;
10729     }
10730
10731     if (done) {
10732         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10733             DrawPosition(FALSE, boards[currentMove]);
10734             DisplayBothClocks();
10735             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10736               DisplayComment(currentMove - 1, commentList[currentMove]);
10737         }
10738         (void) StopLoadGameTimer();
10739         gameFileFP = NULL;
10740         cmailOldMove = forwardMostMove;
10741         return FALSE;
10742     } else {
10743         /* currentMoveString is set as a side-effect of yylex */
10744
10745         thinkOutput[0] = NULLCHAR;
10746         MakeMove(fromX, fromY, toX, toY, promoChar);
10747         currentMove = forwardMostMove;
10748         return TRUE;
10749     }
10750 }
10751
10752 /* Load the nth game from the given file */
10753 int
10754 LoadGameFromFile(filename, n, title, useList)
10755      char *filename;
10756      int n;
10757      char *title;
10758      /*Boolean*/ int useList;
10759 {
10760     FILE *f;
10761     char buf[MSG_SIZ];
10762
10763     if (strcmp(filename, "-") == 0) {
10764         f = stdin;
10765         title = "stdin";
10766     } else {
10767         f = fopen(filename, "rb");
10768         if (f == NULL) {
10769           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10770             DisplayError(buf, errno);
10771             return FALSE;
10772         }
10773     }
10774     if (fseek(f, 0, 0) == -1) {
10775         /* f is not seekable; probably a pipe */
10776         useList = FALSE;
10777     }
10778     if (useList && n == 0) {
10779         int error = GameListBuild(f);
10780         if (error) {
10781             DisplayError(_("Cannot build game list"), error);
10782         } else if (!ListEmpty(&gameList) &&
10783                    ((ListGame *) gameList.tailPred)->number > 1) {
10784             GameListPopUp(f, title);
10785             return TRUE;
10786         }
10787         GameListDestroy();
10788         n = 1;
10789     }
10790     if (n == 0) n = 1;
10791     return LoadGame(f, n, title, FALSE);
10792 }
10793
10794
10795 void
10796 MakeRegisteredMove()
10797 {
10798     int fromX, fromY, toX, toY;
10799     char promoChar;
10800     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10801         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10802           case CMAIL_MOVE:
10803           case CMAIL_DRAW:
10804             if (appData.debugMode)
10805               fprintf(debugFP, "Restoring %s for game %d\n",
10806                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10807
10808             thinkOutput[0] = NULLCHAR;
10809             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10810             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10811             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10812             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10813             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10814             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10815             MakeMove(fromX, fromY, toX, toY, promoChar);
10816             ShowMove(fromX, fromY, toX, toY);
10817
10818             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10819               case MT_NONE:
10820               case MT_CHECK:
10821                 break;
10822
10823               case MT_CHECKMATE:
10824               case MT_STAINMATE:
10825                 if (WhiteOnMove(currentMove)) {
10826                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10827                 } else {
10828                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10829                 }
10830                 break;
10831
10832               case MT_STALEMATE:
10833                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10834                 break;
10835             }
10836
10837             break;
10838
10839           case CMAIL_RESIGN:
10840             if (WhiteOnMove(currentMove)) {
10841                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10842             } else {
10843                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10844             }
10845             break;
10846
10847           case CMAIL_ACCEPT:
10848             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10849             break;
10850
10851           default:
10852             break;
10853         }
10854     }
10855
10856     return;
10857 }
10858
10859 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10860 int
10861 CmailLoadGame(f, gameNumber, title, useList)
10862      FILE *f;
10863      int gameNumber;
10864      char *title;
10865      int useList;
10866 {
10867     int retVal;
10868
10869     if (gameNumber > nCmailGames) {
10870         DisplayError(_("No more games in this message"), 0);
10871         return FALSE;
10872     }
10873     if (f == lastLoadGameFP) {
10874         int offset = gameNumber - lastLoadGameNumber;
10875         if (offset == 0) {
10876             cmailMsg[0] = NULLCHAR;
10877             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10878                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10879                 nCmailMovesRegistered--;
10880             }
10881             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10882             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10883                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10884             }
10885         } else {
10886             if (! RegisterMove()) return FALSE;
10887         }
10888     }
10889
10890     retVal = LoadGame(f, gameNumber, title, useList);
10891
10892     /* Make move registered during previous look at this game, if any */
10893     MakeRegisteredMove();
10894
10895     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10896         commentList[currentMove]
10897           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10898         DisplayComment(currentMove - 1, commentList[currentMove]);
10899     }
10900
10901     return retVal;
10902 }
10903
10904 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10905 int
10906 ReloadGame(offset)
10907      int offset;
10908 {
10909     int gameNumber = lastLoadGameNumber + offset;
10910     if (lastLoadGameFP == NULL) {
10911         DisplayError(_("No game has been loaded yet"), 0);
10912         return FALSE;
10913     }
10914     if (gameNumber <= 0) {
10915         DisplayError(_("Can't back up any further"), 0);
10916         return FALSE;
10917     }
10918     if (cmailMsgLoaded) {
10919         return CmailLoadGame(lastLoadGameFP, gameNumber,
10920                              lastLoadGameTitle, lastLoadGameUseList);
10921     } else {
10922         return LoadGame(lastLoadGameFP, gameNumber,
10923                         lastLoadGameTitle, lastLoadGameUseList);
10924     }
10925 }
10926
10927
10928
10929 /* Load the nth game from open file f */
10930 int
10931 LoadGame(f, gameNumber, title, useList)
10932      FILE *f;
10933      int gameNumber;
10934      char *title;
10935      int useList;
10936 {
10937     ChessMove cm;
10938     char buf[MSG_SIZ];
10939     int gn = gameNumber;
10940     ListGame *lg = NULL;
10941     int numPGNTags = 0;
10942     int err;
10943     GameMode oldGameMode;
10944     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10945
10946     if (appData.debugMode)
10947         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10948
10949     if (gameMode == Training )
10950         SetTrainingModeOff();
10951
10952     oldGameMode = gameMode;
10953     if (gameMode != BeginningOfGame) {
10954       Reset(FALSE, TRUE);
10955     }
10956
10957     gameFileFP = f;
10958     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10959         fclose(lastLoadGameFP);
10960     }
10961
10962     if (useList) {
10963         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10964
10965         if (lg) {
10966             fseek(f, lg->offset, 0);
10967             GameListHighlight(gameNumber);
10968             gn = 1;
10969         }
10970         else {
10971             DisplayError(_("Game number out of range"), 0);
10972             return FALSE;
10973         }
10974     } else {
10975         GameListDestroy();
10976         if (fseek(f, 0, 0) == -1) {
10977             if (f == lastLoadGameFP ?
10978                 gameNumber == lastLoadGameNumber + 1 :
10979                 gameNumber == 1) {
10980                 gn = 1;
10981             } else {
10982                 DisplayError(_("Can't seek on game file"), 0);
10983                 return FALSE;
10984             }
10985         }
10986     }
10987     lastLoadGameFP = f;
10988     lastLoadGameNumber = gameNumber;
10989     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10990     lastLoadGameUseList = useList;
10991
10992     yynewfile(f);
10993
10994     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10995       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10996                 lg->gameInfo.black);
10997             DisplayTitle(buf);
10998     } else if (*title != NULLCHAR) {
10999         if (gameNumber > 1) {
11000           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11001             DisplayTitle(buf);
11002         } else {
11003             DisplayTitle(title);
11004         }
11005     }
11006
11007     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11008         gameMode = PlayFromGameFile;
11009         ModeHighlight();
11010     }
11011
11012     currentMove = forwardMostMove = backwardMostMove = 0;
11013     CopyBoard(boards[0], initialPosition);
11014     StopClocks();
11015
11016     /*
11017      * Skip the first gn-1 games in the file.
11018      * Also skip over anything that precedes an identifiable
11019      * start of game marker, to avoid being confused by
11020      * garbage at the start of the file.  Currently
11021      * recognized start of game markers are the move number "1",
11022      * the pattern "gnuchess .* game", the pattern
11023      * "^[#;%] [^ ]* game file", and a PGN tag block.
11024      * A game that starts with one of the latter two patterns
11025      * will also have a move number 1, possibly
11026      * following a position diagram.
11027      * 5-4-02: Let's try being more lenient and allowing a game to
11028      * start with an unnumbered move.  Does that break anything?
11029      */
11030     cm = lastLoadGameStart = EndOfFile;
11031     while (gn > 0) {
11032         yyboardindex = forwardMostMove;
11033         cm = (ChessMove) Myylex();
11034         switch (cm) {
11035           case EndOfFile:
11036             if (cmailMsgLoaded) {
11037                 nCmailGames = CMAIL_MAX_GAMES - gn;
11038             } else {
11039                 Reset(TRUE, TRUE);
11040                 DisplayError(_("Game not found in file"), 0);
11041             }
11042             return FALSE;
11043
11044           case GNUChessGame:
11045           case XBoardGame:
11046             gn--;
11047             lastLoadGameStart = cm;
11048             break;
11049
11050           case MoveNumberOne:
11051             switch (lastLoadGameStart) {
11052               case GNUChessGame:
11053               case XBoardGame:
11054               case PGNTag:
11055                 break;
11056               case MoveNumberOne:
11057               case EndOfFile:
11058                 gn--;           /* count this game */
11059                 lastLoadGameStart = cm;
11060                 break;
11061               default:
11062                 /* impossible */
11063                 break;
11064             }
11065             break;
11066
11067           case PGNTag:
11068             switch (lastLoadGameStart) {
11069               case GNUChessGame:
11070               case PGNTag:
11071               case MoveNumberOne:
11072               case EndOfFile:
11073                 gn--;           /* count this game */
11074                 lastLoadGameStart = cm;
11075                 break;
11076               case XBoardGame:
11077                 lastLoadGameStart = cm; /* game counted already */
11078                 break;
11079               default:
11080                 /* impossible */
11081                 break;
11082             }
11083             if (gn > 0) {
11084                 do {
11085                     yyboardindex = forwardMostMove;
11086                     cm = (ChessMove) Myylex();
11087                 } while (cm == PGNTag || cm == Comment);
11088             }
11089             break;
11090
11091           case WhiteWins:
11092           case BlackWins:
11093           case GameIsDrawn:
11094             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11095                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11096                     != CMAIL_OLD_RESULT) {
11097                     nCmailResults ++ ;
11098                     cmailResult[  CMAIL_MAX_GAMES
11099                                 - gn - 1] = CMAIL_OLD_RESULT;
11100                 }
11101             }
11102             break;
11103
11104           case NormalMove:
11105             /* Only a NormalMove can be at the start of a game
11106              * without a position diagram. */
11107             if (lastLoadGameStart == EndOfFile ) {
11108               gn--;
11109               lastLoadGameStart = MoveNumberOne;
11110             }
11111             break;
11112
11113           default:
11114             break;
11115         }
11116     }
11117
11118     if (appData.debugMode)
11119       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11120
11121     if (cm == XBoardGame) {
11122         /* Skip any header junk before position diagram and/or move 1 */
11123         for (;;) {
11124             yyboardindex = forwardMostMove;
11125             cm = (ChessMove) Myylex();
11126
11127             if (cm == EndOfFile ||
11128                 cm == GNUChessGame || cm == XBoardGame) {
11129                 /* Empty game; pretend end-of-file and handle later */
11130                 cm = EndOfFile;
11131                 break;
11132             }
11133
11134             if (cm == MoveNumberOne || cm == PositionDiagram ||
11135                 cm == PGNTag || cm == Comment)
11136               break;
11137         }
11138     } else if (cm == GNUChessGame) {
11139         if (gameInfo.event != NULL) {
11140             free(gameInfo.event);
11141         }
11142         gameInfo.event = StrSave(yy_text);
11143     }
11144
11145     startedFromSetupPosition = FALSE;
11146     while (cm == PGNTag) {
11147         if (appData.debugMode)
11148           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11149         err = ParsePGNTag(yy_text, &gameInfo);
11150         if (!err) numPGNTags++;
11151
11152         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11153         if(gameInfo.variant != oldVariant) {
11154             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11155             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11156             InitPosition(TRUE);
11157             oldVariant = gameInfo.variant;
11158             if (appData.debugMode)
11159               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11160         }
11161
11162
11163         if (gameInfo.fen != NULL) {
11164           Board initial_position;
11165           startedFromSetupPosition = TRUE;
11166           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11167             Reset(TRUE, TRUE);
11168             DisplayError(_("Bad FEN position in file"), 0);
11169             return FALSE;
11170           }
11171           CopyBoard(boards[0], initial_position);
11172           if (blackPlaysFirst) {
11173             currentMove = forwardMostMove = backwardMostMove = 1;
11174             CopyBoard(boards[1], initial_position);
11175             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11176             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11177             timeRemaining[0][1] = whiteTimeRemaining;
11178             timeRemaining[1][1] = blackTimeRemaining;
11179             if (commentList[0] != NULL) {
11180               commentList[1] = commentList[0];
11181               commentList[0] = NULL;
11182             }
11183           } else {
11184             currentMove = forwardMostMove = backwardMostMove = 0;
11185           }
11186           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11187           {   int i;
11188               initialRulePlies = FENrulePlies;
11189               for( i=0; i< nrCastlingRights; i++ )
11190                   initialRights[i] = initial_position[CASTLING][i];
11191           }
11192           yyboardindex = forwardMostMove;
11193           free(gameInfo.fen);
11194           gameInfo.fen = NULL;
11195         }
11196
11197         yyboardindex = forwardMostMove;
11198         cm = (ChessMove) Myylex();
11199
11200         /* Handle comments interspersed among the tags */
11201         while (cm == Comment) {
11202             char *p;
11203             if (appData.debugMode)
11204               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11205             p = yy_text;
11206             AppendComment(currentMove, p, FALSE);
11207             yyboardindex = forwardMostMove;
11208             cm = (ChessMove) Myylex();
11209         }
11210     }
11211
11212     /* don't rely on existence of Event tag since if game was
11213      * pasted from clipboard the Event tag may not exist
11214      */
11215     if (numPGNTags > 0){
11216         char *tags;
11217         if (gameInfo.variant == VariantNormal) {
11218           VariantClass v = StringToVariant(gameInfo.event);
11219           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11220           if(v < VariantShogi) gameInfo.variant = v;
11221         }
11222         if (!matchMode) {
11223           if( appData.autoDisplayTags ) {
11224             tags = PGNTags(&gameInfo);
11225             TagsPopUp(tags, CmailMsg());
11226             free(tags);
11227           }
11228         }
11229     } else {
11230         /* Make something up, but don't display it now */
11231         SetGameInfo();
11232         TagsPopDown();
11233     }
11234
11235     if (cm == PositionDiagram) {
11236         int i, j;
11237         char *p;
11238         Board initial_position;
11239
11240         if (appData.debugMode)
11241           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11242
11243         if (!startedFromSetupPosition) {
11244             p = yy_text;
11245             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11246               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11247                 switch (*p) {
11248                   case '{':
11249                   case '[':
11250                   case '-':
11251                   case ' ':
11252                   case '\t':
11253                   case '\n':
11254                   case '\r':
11255                     break;
11256                   default:
11257                     initial_position[i][j++] = CharToPiece(*p);
11258                     break;
11259                 }
11260             while (*p == ' ' || *p == '\t' ||
11261                    *p == '\n' || *p == '\r') p++;
11262
11263             if (strncmp(p, "black", strlen("black"))==0)
11264               blackPlaysFirst = TRUE;
11265             else
11266               blackPlaysFirst = FALSE;
11267             startedFromSetupPosition = TRUE;
11268
11269             CopyBoard(boards[0], initial_position);
11270             if (blackPlaysFirst) {
11271                 currentMove = forwardMostMove = backwardMostMove = 1;
11272                 CopyBoard(boards[1], initial_position);
11273                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11274                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11275                 timeRemaining[0][1] = whiteTimeRemaining;
11276                 timeRemaining[1][1] = blackTimeRemaining;
11277                 if (commentList[0] != NULL) {
11278                     commentList[1] = commentList[0];
11279                     commentList[0] = NULL;
11280                 }
11281             } else {
11282                 currentMove = forwardMostMove = backwardMostMove = 0;
11283             }
11284         }
11285         yyboardindex = forwardMostMove;
11286         cm = (ChessMove) Myylex();
11287     }
11288
11289     if (first.pr == NoProc) {
11290         StartChessProgram(&first);
11291     }
11292     InitChessProgram(&first, FALSE);
11293     SendToProgram("force\n", &first);
11294     if (startedFromSetupPosition) {
11295         SendBoard(&first, forwardMostMove);
11296     if (appData.debugMode) {
11297         fprintf(debugFP, "Load Game\n");
11298     }
11299         DisplayBothClocks();
11300     }
11301
11302     /* [HGM] server: flag to write setup moves in broadcast file as one */
11303     loadFlag = appData.suppressLoadMoves;
11304
11305     while (cm == Comment) {
11306         char *p;
11307         if (appData.debugMode)
11308           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11309         p = yy_text;
11310         AppendComment(currentMove, p, FALSE);
11311         yyboardindex = forwardMostMove;
11312         cm = (ChessMove) Myylex();
11313     }
11314
11315     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11316         cm == WhiteWins || cm == BlackWins ||
11317         cm == GameIsDrawn || cm == GameUnfinished) {
11318         DisplayMessage("", _("No moves in game"));
11319         if (cmailMsgLoaded) {
11320             if (appData.debugMode)
11321               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11322             ClearHighlights();
11323             flipView = FALSE;
11324         }
11325         DrawPosition(FALSE, boards[currentMove]);
11326         DisplayBothClocks();
11327         gameMode = EditGame;
11328         ModeHighlight();
11329         gameFileFP = NULL;
11330         cmailOldMove = 0;
11331         return TRUE;
11332     }
11333
11334     // [HGM] PV info: routine tests if comment empty
11335     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11336         DisplayComment(currentMove - 1, commentList[currentMove]);
11337     }
11338     if (!matchMode && appData.timeDelay != 0)
11339       DrawPosition(FALSE, boards[currentMove]);
11340
11341     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11342       programStats.ok_to_send = 1;
11343     }
11344
11345     /* if the first token after the PGN tags is a move
11346      * and not move number 1, retrieve it from the parser
11347      */
11348     if (cm != MoveNumberOne)
11349         LoadGameOneMove(cm);
11350
11351     /* load the remaining moves from the file */
11352     while (LoadGameOneMove(EndOfFile)) {
11353       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11354       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11355     }
11356
11357     /* rewind to the start of the game */
11358     currentMove = backwardMostMove;
11359
11360     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11361
11362     if (oldGameMode == AnalyzeFile ||
11363         oldGameMode == AnalyzeMode) {
11364       AnalyzeFileEvent();
11365     }
11366
11367     if (matchMode || appData.timeDelay == 0) {
11368       ToEndEvent();
11369       gameMode = EditGame;
11370       ModeHighlight();
11371     } else if (appData.timeDelay > 0) {
11372       AutoPlayGameLoop();
11373     }
11374
11375     if (appData.debugMode)
11376         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11377
11378     loadFlag = 0; /* [HGM] true game starts */
11379     return TRUE;
11380 }
11381
11382 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11383 int
11384 ReloadPosition(offset)
11385      int offset;
11386 {
11387     int positionNumber = lastLoadPositionNumber + offset;
11388     if (lastLoadPositionFP == NULL) {
11389         DisplayError(_("No position has been loaded yet"), 0);
11390         return FALSE;
11391     }
11392     if (positionNumber <= 0) {
11393         DisplayError(_("Can't back up any further"), 0);
11394         return FALSE;
11395     }
11396     return LoadPosition(lastLoadPositionFP, positionNumber,
11397                         lastLoadPositionTitle);
11398 }
11399
11400 /* Load the nth position from the given file */
11401 int
11402 LoadPositionFromFile(filename, n, title)
11403      char *filename;
11404      int n;
11405      char *title;
11406 {
11407     FILE *f;
11408     char buf[MSG_SIZ];
11409
11410     if (strcmp(filename, "-") == 0) {
11411         return LoadPosition(stdin, n, "stdin");
11412     } else {
11413         f = fopen(filename, "rb");
11414         if (f == NULL) {
11415             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11416             DisplayError(buf, errno);
11417             return FALSE;
11418         } else {
11419             return LoadPosition(f, n, title);
11420         }
11421     }
11422 }
11423
11424 /* Load the nth position from the given open file, and close it */
11425 int
11426 LoadPosition(f, positionNumber, title)
11427      FILE *f;
11428      int positionNumber;
11429      char *title;
11430 {
11431     char *p, line[MSG_SIZ];
11432     Board initial_position;
11433     int i, j, fenMode, pn;
11434
11435     if (gameMode == Training )
11436         SetTrainingModeOff();
11437
11438     if (gameMode != BeginningOfGame) {
11439         Reset(FALSE, TRUE);
11440     }
11441     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11442         fclose(lastLoadPositionFP);
11443     }
11444     if (positionNumber == 0) positionNumber = 1;
11445     lastLoadPositionFP = f;
11446     lastLoadPositionNumber = positionNumber;
11447     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11448     if (first.pr == NoProc) {
11449       StartChessProgram(&first);
11450       InitChessProgram(&first, FALSE);
11451     }
11452     pn = positionNumber;
11453     if (positionNumber < 0) {
11454         /* Negative position number means to seek to that byte offset */
11455         if (fseek(f, -positionNumber, 0) == -1) {
11456             DisplayError(_("Can't seek on position file"), 0);
11457             return FALSE;
11458         };
11459         pn = 1;
11460     } else {
11461         if (fseek(f, 0, 0) == -1) {
11462             if (f == lastLoadPositionFP ?
11463                 positionNumber == lastLoadPositionNumber + 1 :
11464                 positionNumber == 1) {
11465                 pn = 1;
11466             } else {
11467                 DisplayError(_("Can't seek on position file"), 0);
11468                 return FALSE;
11469             }
11470         }
11471     }
11472     /* See if this file is FEN or old-style xboard */
11473     if (fgets(line, MSG_SIZ, f) == NULL) {
11474         DisplayError(_("Position not found in file"), 0);
11475         return FALSE;
11476     }
11477     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11478     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11479
11480     if (pn >= 2) {
11481         if (fenMode || line[0] == '#') pn--;
11482         while (pn > 0) {
11483             /* skip positions before number pn */
11484             if (fgets(line, MSG_SIZ, f) == NULL) {
11485                 Reset(TRUE, TRUE);
11486                 DisplayError(_("Position not found in file"), 0);
11487                 return FALSE;
11488             }
11489             if (fenMode || line[0] == '#') pn--;
11490         }
11491     }
11492
11493     if (fenMode) {
11494         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11495             DisplayError(_("Bad FEN position in file"), 0);
11496             return FALSE;
11497         }
11498     } else {
11499         (void) fgets(line, MSG_SIZ, f);
11500         (void) fgets(line, MSG_SIZ, f);
11501
11502         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11503             (void) fgets(line, MSG_SIZ, f);
11504             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11505                 if (*p == ' ')
11506                   continue;
11507                 initial_position[i][j++] = CharToPiece(*p);
11508             }
11509         }
11510
11511         blackPlaysFirst = FALSE;
11512         if (!feof(f)) {
11513             (void) fgets(line, MSG_SIZ, f);
11514             if (strncmp(line, "black", strlen("black"))==0)
11515               blackPlaysFirst = TRUE;
11516         }
11517     }
11518     startedFromSetupPosition = TRUE;
11519
11520     SendToProgram("force\n", &first);
11521     CopyBoard(boards[0], initial_position);
11522     if (blackPlaysFirst) {
11523         currentMove = forwardMostMove = backwardMostMove = 1;
11524         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11525         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11526         CopyBoard(boards[1], initial_position);
11527         DisplayMessage("", _("Black to play"));
11528     } else {
11529         currentMove = forwardMostMove = backwardMostMove = 0;
11530         DisplayMessage("", _("White to play"));
11531     }
11532     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11533     SendBoard(&first, forwardMostMove);
11534     if (appData.debugMode) {
11535 int i, j;
11536   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11537   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11538         fprintf(debugFP, "Load Position\n");
11539     }
11540
11541     if (positionNumber > 1) {
11542       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11543         DisplayTitle(line);
11544     } else {
11545         DisplayTitle(title);
11546     }
11547     gameMode = EditGame;
11548     ModeHighlight();
11549     ResetClocks();
11550     timeRemaining[0][1] = whiteTimeRemaining;
11551     timeRemaining[1][1] = blackTimeRemaining;
11552     DrawPosition(FALSE, boards[currentMove]);
11553
11554     return TRUE;
11555 }
11556
11557
11558 void
11559 CopyPlayerNameIntoFileName(dest, src)
11560      char **dest, *src;
11561 {
11562     while (*src != NULLCHAR && *src != ',') {
11563         if (*src == ' ') {
11564             *(*dest)++ = '_';
11565             src++;
11566         } else {
11567             *(*dest)++ = *src++;
11568         }
11569     }
11570 }
11571
11572 char *DefaultFileName(ext)
11573      char *ext;
11574 {
11575     static char def[MSG_SIZ];
11576     char *p;
11577
11578     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11579         p = def;
11580         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11581         *p++ = '-';
11582         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11583         *p++ = '.';
11584         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11585     } else {
11586         def[0] = NULLCHAR;
11587     }
11588     return def;
11589 }
11590
11591 /* Save the current game to the given file */
11592 int
11593 SaveGameToFile(filename, append)
11594      char *filename;
11595      int append;
11596 {
11597     FILE *f;
11598     char buf[MSG_SIZ];
11599     int result;
11600
11601     if (strcmp(filename, "-") == 0) {
11602         return SaveGame(stdout, 0, NULL);
11603     } else {
11604         f = fopen(filename, append ? "a" : "w");
11605         if (f == NULL) {
11606             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11607             DisplayError(buf, errno);
11608             return FALSE;
11609         } else {
11610             safeStrCpy(buf, lastMsg, MSG_SIZ);
11611             DisplayMessage(_("Waiting for access to save file"), "");
11612             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11613             DisplayMessage(_("Saving game"), "");
11614             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11615             result = SaveGame(f, 0, NULL);
11616             DisplayMessage(buf, "");
11617             return result;
11618         }
11619     }
11620 }
11621
11622 char *
11623 SavePart(str)
11624      char *str;
11625 {
11626     static char buf[MSG_SIZ];
11627     char *p;
11628
11629     p = strchr(str, ' ');
11630     if (p == NULL) return str;
11631     strncpy(buf, str, p - str);
11632     buf[p - str] = NULLCHAR;
11633     return buf;
11634 }
11635
11636 #define PGN_MAX_LINE 75
11637
11638 #define PGN_SIDE_WHITE  0
11639 #define PGN_SIDE_BLACK  1
11640
11641 /* [AS] */
11642 static int FindFirstMoveOutOfBook( int side )
11643 {
11644     int result = -1;
11645
11646     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11647         int index = backwardMostMove;
11648         int has_book_hit = 0;
11649
11650         if( (index % 2) != side ) {
11651             index++;
11652         }
11653
11654         while( index < forwardMostMove ) {
11655             /* Check to see if engine is in book */
11656             int depth = pvInfoList[index].depth;
11657             int score = pvInfoList[index].score;
11658             int in_book = 0;
11659
11660             if( depth <= 2 ) {
11661                 in_book = 1;
11662             }
11663             else if( score == 0 && depth == 63 ) {
11664                 in_book = 1; /* Zappa */
11665             }
11666             else if( score == 2 && depth == 99 ) {
11667                 in_book = 1; /* Abrok */
11668             }
11669
11670             has_book_hit += in_book;
11671
11672             if( ! in_book ) {
11673                 result = index;
11674
11675                 break;
11676             }
11677
11678             index += 2;
11679         }
11680     }
11681
11682     return result;
11683 }
11684
11685 /* [AS] */
11686 void GetOutOfBookInfo( char * buf )
11687 {
11688     int oob[2];
11689     int i;
11690     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11691
11692     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11693     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11694
11695     *buf = '\0';
11696
11697     if( oob[0] >= 0 || oob[1] >= 0 ) {
11698         for( i=0; i<2; i++ ) {
11699             int idx = oob[i];
11700
11701             if( idx >= 0 ) {
11702                 if( i > 0 && oob[0] >= 0 ) {
11703                     strcat( buf, "   " );
11704                 }
11705
11706                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11707                 sprintf( buf+strlen(buf), "%s%.2f",
11708                     pvInfoList[idx].score >= 0 ? "+" : "",
11709                     pvInfoList[idx].score / 100.0 );
11710             }
11711         }
11712     }
11713 }
11714
11715 /* Save game in PGN style and close the file */
11716 int
11717 SaveGamePGN(f)
11718      FILE *f;
11719 {
11720     int i, offset, linelen, newblock;
11721     time_t tm;
11722 //    char *movetext;
11723     char numtext[32];
11724     int movelen, numlen, blank;
11725     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11726
11727     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11728
11729     tm = time((time_t *) NULL);
11730
11731     PrintPGNTags(f, &gameInfo);
11732
11733     if (backwardMostMove > 0 || startedFromSetupPosition) {
11734         char *fen = PositionToFEN(backwardMostMove, NULL);
11735         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11736         fprintf(f, "\n{--------------\n");
11737         PrintPosition(f, backwardMostMove);
11738         fprintf(f, "--------------}\n");
11739         free(fen);
11740     }
11741     else {
11742         /* [AS] Out of book annotation */
11743         if( appData.saveOutOfBookInfo ) {
11744             char buf[64];
11745
11746             GetOutOfBookInfo( buf );
11747
11748             if( buf[0] != '\0' ) {
11749                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11750             }
11751         }
11752
11753         fprintf(f, "\n");
11754     }
11755
11756     i = backwardMostMove;
11757     linelen = 0;
11758     newblock = TRUE;
11759
11760     while (i < forwardMostMove) {
11761         /* Print comments preceding this move */
11762         if (commentList[i] != NULL) {
11763             if (linelen > 0) fprintf(f, "\n");
11764             fprintf(f, "%s", commentList[i]);
11765             linelen = 0;
11766             newblock = TRUE;
11767         }
11768
11769         /* Format move number */
11770         if ((i % 2) == 0)
11771           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11772         else
11773           if (newblock)
11774             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11775           else
11776             numtext[0] = NULLCHAR;
11777
11778         numlen = strlen(numtext);
11779         newblock = FALSE;
11780
11781         /* Print move number */
11782         blank = linelen > 0 && numlen > 0;
11783         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11784             fprintf(f, "\n");
11785             linelen = 0;
11786             blank = 0;
11787         }
11788         if (blank) {
11789             fprintf(f, " ");
11790             linelen++;
11791         }
11792         fprintf(f, "%s", numtext);
11793         linelen += numlen;
11794
11795         /* Get move */
11796         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11797         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11798
11799         /* Print move */
11800         blank = linelen > 0 && movelen > 0;
11801         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11802             fprintf(f, "\n");
11803             linelen = 0;
11804             blank = 0;
11805         }
11806         if (blank) {
11807             fprintf(f, " ");
11808             linelen++;
11809         }
11810         fprintf(f, "%s", move_buffer);
11811         linelen += movelen;
11812
11813         /* [AS] Add PV info if present */
11814         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11815             /* [HGM] add time */
11816             char buf[MSG_SIZ]; int seconds;
11817
11818             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11819
11820             if( seconds <= 0)
11821               buf[0] = 0;
11822             else
11823               if( seconds < 30 )
11824                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11825               else
11826                 {
11827                   seconds = (seconds + 4)/10; // round to full seconds
11828                   if( seconds < 60 )
11829                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11830                   else
11831                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11832                 }
11833
11834             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11835                       pvInfoList[i].score >= 0 ? "+" : "",
11836                       pvInfoList[i].score / 100.0,
11837                       pvInfoList[i].depth,
11838                       buf );
11839
11840             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11841
11842             /* Print score/depth */
11843             blank = linelen > 0 && movelen > 0;
11844             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11845                 fprintf(f, "\n");
11846                 linelen = 0;
11847                 blank = 0;
11848             }
11849             if (blank) {
11850                 fprintf(f, " ");
11851                 linelen++;
11852             }
11853             fprintf(f, "%s", move_buffer);
11854             linelen += movelen;
11855         }
11856
11857         i++;
11858     }
11859
11860     /* Start a new line */
11861     if (linelen > 0) fprintf(f, "\n");
11862
11863     /* Print comments after last move */
11864     if (commentList[i] != NULL) {
11865         fprintf(f, "%s\n", commentList[i]);
11866     }
11867
11868     /* Print result */
11869     if (gameInfo.resultDetails != NULL &&
11870         gameInfo.resultDetails[0] != NULLCHAR) {
11871         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11872                 PGNResult(gameInfo.result));
11873     } else {
11874         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11875     }
11876
11877     fclose(f);
11878     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11879     return TRUE;
11880 }
11881
11882 /* Save game in old style and close the file */
11883 int
11884 SaveGameOldStyle(f)
11885      FILE *f;
11886 {
11887     int i, offset;
11888     time_t tm;
11889
11890     tm = time((time_t *) NULL);
11891
11892     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11893     PrintOpponents(f);
11894
11895     if (backwardMostMove > 0 || startedFromSetupPosition) {
11896         fprintf(f, "\n[--------------\n");
11897         PrintPosition(f, backwardMostMove);
11898         fprintf(f, "--------------]\n");
11899     } else {
11900         fprintf(f, "\n");
11901     }
11902
11903     i = backwardMostMove;
11904     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11905
11906     while (i < forwardMostMove) {
11907         if (commentList[i] != NULL) {
11908             fprintf(f, "[%s]\n", commentList[i]);
11909         }
11910
11911         if ((i % 2) == 1) {
11912             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11913             i++;
11914         } else {
11915             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11916             i++;
11917             if (commentList[i] != NULL) {
11918                 fprintf(f, "\n");
11919                 continue;
11920             }
11921             if (i >= forwardMostMove) {
11922                 fprintf(f, "\n");
11923                 break;
11924             }
11925             fprintf(f, "%s\n", parseList[i]);
11926             i++;
11927         }
11928     }
11929
11930     if (commentList[i] != NULL) {
11931         fprintf(f, "[%s]\n", commentList[i]);
11932     }
11933
11934     /* This isn't really the old style, but it's close enough */
11935     if (gameInfo.resultDetails != NULL &&
11936         gameInfo.resultDetails[0] != NULLCHAR) {
11937         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11938                 gameInfo.resultDetails);
11939     } else {
11940         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11941     }
11942
11943     fclose(f);
11944     return TRUE;
11945 }
11946
11947 /* Save the current game to open file f and close the file */
11948 int
11949 SaveGame(f, dummy, dummy2)
11950      FILE *f;
11951      int dummy;
11952      char *dummy2;
11953 {
11954     if (gameMode == EditPosition) EditPositionDone(TRUE);
11955     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11956     if (appData.oldSaveStyle)
11957       return SaveGameOldStyle(f);
11958     else
11959       return SaveGamePGN(f);
11960 }
11961
11962 /* Save the current position to the given file */
11963 int
11964 SavePositionToFile(filename)
11965      char *filename;
11966 {
11967     FILE *f;
11968     char buf[MSG_SIZ];
11969
11970     if (strcmp(filename, "-") == 0) {
11971         return SavePosition(stdout, 0, NULL);
11972     } else {
11973         f = fopen(filename, "a");
11974         if (f == NULL) {
11975             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11976             DisplayError(buf, errno);
11977             return FALSE;
11978         } else {
11979             safeStrCpy(buf, lastMsg, MSG_SIZ);
11980             DisplayMessage(_("Waiting for access to save file"), "");
11981             flock(fileno(f), LOCK_EX); // [HGM] lock
11982             DisplayMessage(_("Saving position"), "");
11983             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11984             SavePosition(f, 0, NULL);
11985             DisplayMessage(buf, "");
11986             return TRUE;
11987         }
11988     }
11989 }
11990
11991 /* Save the current position to the given open file and close the file */
11992 int
11993 SavePosition(f, dummy, dummy2)
11994      FILE *f;
11995      int dummy;
11996      char *dummy2;
11997 {
11998     time_t tm;
11999     char *fen;
12000
12001     if (gameMode == EditPosition) EditPositionDone(TRUE);
12002     if (appData.oldSaveStyle) {
12003         tm = time((time_t *) NULL);
12004
12005         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12006         PrintOpponents(f);
12007         fprintf(f, "[--------------\n");
12008         PrintPosition(f, currentMove);
12009         fprintf(f, "--------------]\n");
12010     } else {
12011         fen = PositionToFEN(currentMove, NULL);
12012         fprintf(f, "%s\n", fen);
12013         free(fen);
12014     }
12015     fclose(f);
12016     return TRUE;
12017 }
12018
12019 void
12020 ReloadCmailMsgEvent(unregister)
12021      int unregister;
12022 {
12023 #if !WIN32
12024     static char *inFilename = NULL;
12025     static char *outFilename;
12026     int i;
12027     struct stat inbuf, outbuf;
12028     int status;
12029
12030     /* Any registered moves are unregistered if unregister is set, */
12031     /* i.e. invoked by the signal handler */
12032     if (unregister) {
12033         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12034             cmailMoveRegistered[i] = FALSE;
12035             if (cmailCommentList[i] != NULL) {
12036                 free(cmailCommentList[i]);
12037                 cmailCommentList[i] = NULL;
12038             }
12039         }
12040         nCmailMovesRegistered = 0;
12041     }
12042
12043     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12044         cmailResult[i] = CMAIL_NOT_RESULT;
12045     }
12046     nCmailResults = 0;
12047
12048     if (inFilename == NULL) {
12049         /* Because the filenames are static they only get malloced once  */
12050         /* and they never get freed                                      */
12051         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12052         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12053
12054         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12055         sprintf(outFilename, "%s.out", appData.cmailGameName);
12056     }
12057
12058     status = stat(outFilename, &outbuf);
12059     if (status < 0) {
12060         cmailMailedMove = FALSE;
12061     } else {
12062         status = stat(inFilename, &inbuf);
12063         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12064     }
12065
12066     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12067        counts the games, notes how each one terminated, etc.
12068
12069        It would be nice to remove this kludge and instead gather all
12070        the information while building the game list.  (And to keep it
12071        in the game list nodes instead of having a bunch of fixed-size
12072        parallel arrays.)  Note this will require getting each game's
12073        termination from the PGN tags, as the game list builder does
12074        not process the game moves.  --mann
12075        */
12076     cmailMsgLoaded = TRUE;
12077     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12078
12079     /* Load first game in the file or popup game menu */
12080     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12081
12082 #endif /* !WIN32 */
12083     return;
12084 }
12085
12086 int
12087 RegisterMove()
12088 {
12089     FILE *f;
12090     char string[MSG_SIZ];
12091
12092     if (   cmailMailedMove
12093         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12094         return TRUE;            /* Allow free viewing  */
12095     }
12096
12097     /* Unregister move to ensure that we don't leave RegisterMove        */
12098     /* with the move registered when the conditions for registering no   */
12099     /* longer hold                                                       */
12100     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12101         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12102         nCmailMovesRegistered --;
12103
12104         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12105           {
12106               free(cmailCommentList[lastLoadGameNumber - 1]);
12107               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12108           }
12109     }
12110
12111     if (cmailOldMove == -1) {
12112         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12113         return FALSE;
12114     }
12115
12116     if (currentMove > cmailOldMove + 1) {
12117         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12118         return FALSE;
12119     }
12120
12121     if (currentMove < cmailOldMove) {
12122         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12123         return FALSE;
12124     }
12125
12126     if (forwardMostMove > currentMove) {
12127         /* Silently truncate extra moves */
12128         TruncateGame();
12129     }
12130
12131     if (   (currentMove == cmailOldMove + 1)
12132         || (   (currentMove == cmailOldMove)
12133             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12134                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12135         if (gameInfo.result != GameUnfinished) {
12136             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12137         }
12138
12139         if (commentList[currentMove] != NULL) {
12140             cmailCommentList[lastLoadGameNumber - 1]
12141               = StrSave(commentList[currentMove]);
12142         }
12143         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12144
12145         if (appData.debugMode)
12146           fprintf(debugFP, "Saving %s for game %d\n",
12147                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12148
12149         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12150
12151         f = fopen(string, "w");
12152         if (appData.oldSaveStyle) {
12153             SaveGameOldStyle(f); /* also closes the file */
12154
12155             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12156             f = fopen(string, "w");
12157             SavePosition(f, 0, NULL); /* also closes the file */
12158         } else {
12159             fprintf(f, "{--------------\n");
12160             PrintPosition(f, currentMove);
12161             fprintf(f, "--------------}\n\n");
12162
12163             SaveGame(f, 0, NULL); /* also closes the file*/
12164         }
12165
12166         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12167         nCmailMovesRegistered ++;
12168     } else if (nCmailGames == 1) {
12169         DisplayError(_("You have not made a move yet"), 0);
12170         return FALSE;
12171     }
12172
12173     return TRUE;
12174 }
12175
12176 void
12177 MailMoveEvent()
12178 {
12179 #if !WIN32
12180     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12181     FILE *commandOutput;
12182     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12183     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12184     int nBuffers;
12185     int i;
12186     int archived;
12187     char *arcDir;
12188
12189     if (! cmailMsgLoaded) {
12190         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12191         return;
12192     }
12193
12194     if (nCmailGames == nCmailResults) {
12195         DisplayError(_("No unfinished games"), 0);
12196         return;
12197     }
12198
12199 #if CMAIL_PROHIBIT_REMAIL
12200     if (cmailMailedMove) {
12201       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);
12202         DisplayError(msg, 0);
12203         return;
12204     }
12205 #endif
12206
12207     if (! (cmailMailedMove || RegisterMove())) return;
12208
12209     if (   cmailMailedMove
12210         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12211       snprintf(string, MSG_SIZ, partCommandString,
12212                appData.debugMode ? " -v" : "", appData.cmailGameName);
12213         commandOutput = popen(string, "r");
12214
12215         if (commandOutput == NULL) {
12216             DisplayError(_("Failed to invoke cmail"), 0);
12217         } else {
12218             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12219                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12220             }
12221             if (nBuffers > 1) {
12222                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12223                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12224                 nBytes = MSG_SIZ - 1;
12225             } else {
12226                 (void) memcpy(msg, buffer, nBytes);
12227             }
12228             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12229
12230             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12231                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12232
12233                 archived = TRUE;
12234                 for (i = 0; i < nCmailGames; i ++) {
12235                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12236                         archived = FALSE;
12237                     }
12238                 }
12239                 if (   archived
12240                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12241                         != NULL)) {
12242                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12243                            arcDir,
12244                            appData.cmailGameName,
12245                            gameInfo.date);
12246                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12247                     cmailMsgLoaded = FALSE;
12248                 }
12249             }
12250
12251             DisplayInformation(msg);
12252             pclose(commandOutput);
12253         }
12254     } else {
12255         if ((*cmailMsg) != '\0') {
12256             DisplayInformation(cmailMsg);
12257         }
12258     }
12259
12260     return;
12261 #endif /* !WIN32 */
12262 }
12263
12264 char *
12265 CmailMsg()
12266 {
12267 #if WIN32
12268     return NULL;
12269 #else
12270     int  prependComma = 0;
12271     char number[5];
12272     char string[MSG_SIZ];       /* Space for game-list */
12273     int  i;
12274
12275     if (!cmailMsgLoaded) return "";
12276
12277     if (cmailMailedMove) {
12278       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12279     } else {
12280         /* Create a list of games left */
12281       snprintf(string, MSG_SIZ, "[");
12282         for (i = 0; i < nCmailGames; i ++) {
12283             if (! (   cmailMoveRegistered[i]
12284                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12285                 if (prependComma) {
12286                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12287                 } else {
12288                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12289                     prependComma = 1;
12290                 }
12291
12292                 strcat(string, number);
12293             }
12294         }
12295         strcat(string, "]");
12296
12297         if (nCmailMovesRegistered + nCmailResults == 0) {
12298             switch (nCmailGames) {
12299               case 1:
12300                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12301                 break;
12302
12303               case 2:
12304                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12305                 break;
12306
12307               default:
12308                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12309                          nCmailGames);
12310                 break;
12311             }
12312         } else {
12313             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12314               case 1:
12315                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12316                          string);
12317                 break;
12318
12319               case 0:
12320                 if (nCmailResults == nCmailGames) {
12321                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12322                 } else {
12323                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12324                 }
12325                 break;
12326
12327               default:
12328                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12329                          string);
12330             }
12331         }
12332     }
12333     return cmailMsg;
12334 #endif /* WIN32 */
12335 }
12336
12337 void
12338 ResetGameEvent()
12339 {
12340     if (gameMode == Training)
12341       SetTrainingModeOff();
12342
12343     Reset(TRUE, TRUE);
12344     cmailMsgLoaded = FALSE;
12345     if (appData.icsActive) {
12346       SendToICS(ics_prefix);
12347       SendToICS("refresh\n");
12348     }
12349 }
12350
12351 void
12352 ExitEvent(status)
12353      int status;
12354 {
12355     exiting++;
12356     if (exiting > 2) {
12357       /* Give up on clean exit */
12358       exit(status);
12359     }
12360     if (exiting > 1) {
12361       /* Keep trying for clean exit */
12362       return;
12363     }
12364
12365     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12366
12367     if (telnetISR != NULL) {
12368       RemoveInputSource(telnetISR);
12369     }
12370     if (icsPR != NoProc) {
12371       DestroyChildProcess(icsPR, TRUE);
12372     }
12373
12374     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12375     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12376
12377     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12378     /* make sure this other one finishes before killing it!                  */
12379     if(endingGame) { int count = 0;
12380         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12381         while(endingGame && count++ < 10) DoSleep(1);
12382         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12383     }
12384
12385     /* Kill off chess programs */
12386     if (first.pr != NoProc) {
12387         ExitAnalyzeMode();
12388
12389         DoSleep( appData.delayBeforeQuit );
12390         SendToProgram("quit\n", &first);
12391         DoSleep( appData.delayAfterQuit );
12392         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12393     }
12394     if (second.pr != NoProc) {
12395         DoSleep( appData.delayBeforeQuit );
12396         SendToProgram("quit\n", &second);
12397         DoSleep( appData.delayAfterQuit );
12398         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12399     }
12400     if (first.isr != NULL) {
12401         RemoveInputSource(first.isr);
12402     }
12403     if (second.isr != NULL) {
12404         RemoveInputSource(second.isr);
12405     }
12406
12407     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12408     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12409
12410     ShutDownFrontEnd();
12411     exit(status);
12412 }
12413
12414 void
12415 PauseEvent()
12416 {
12417     if (appData.debugMode)
12418         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12419     if (pausing) {
12420         pausing = FALSE;
12421         ModeHighlight();
12422         if (gameMode == MachinePlaysWhite ||
12423             gameMode == MachinePlaysBlack) {
12424             StartClocks();
12425         } else {
12426             DisplayBothClocks();
12427         }
12428         if (gameMode == PlayFromGameFile) {
12429             if (appData.timeDelay >= 0)
12430                 AutoPlayGameLoop();
12431         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12432             Reset(FALSE, TRUE);
12433             SendToICS(ics_prefix);
12434             SendToICS("refresh\n");
12435         } else if (currentMove < forwardMostMove) {
12436             ForwardInner(forwardMostMove);
12437         }
12438         pauseExamInvalid = FALSE;
12439     } else {
12440         switch (gameMode) {
12441           default:
12442             return;
12443           case IcsExamining:
12444             pauseExamForwardMostMove = forwardMostMove;
12445             pauseExamInvalid = FALSE;
12446             /* fall through */
12447           case IcsObserving:
12448           case IcsPlayingWhite:
12449           case IcsPlayingBlack:
12450             pausing = TRUE;
12451             ModeHighlight();
12452             return;
12453           case PlayFromGameFile:
12454             (void) StopLoadGameTimer();
12455             pausing = TRUE;
12456             ModeHighlight();
12457             break;
12458           case BeginningOfGame:
12459             if (appData.icsActive) return;
12460             /* else fall through */
12461           case MachinePlaysWhite:
12462           case MachinePlaysBlack:
12463           case TwoMachinesPlay:
12464             if (forwardMostMove == 0)
12465               return;           /* don't pause if no one has moved */
12466             if ((gameMode == MachinePlaysWhite &&
12467                  !WhiteOnMove(forwardMostMove)) ||
12468                 (gameMode == MachinePlaysBlack &&
12469                  WhiteOnMove(forwardMostMove))) {
12470                 StopClocks();
12471             }
12472             pausing = TRUE;
12473             ModeHighlight();
12474             break;
12475         }
12476     }
12477 }
12478
12479 void
12480 EditCommentEvent()
12481 {
12482     char title[MSG_SIZ];
12483
12484     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12485       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12486     } else {
12487       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12488                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12489                parseList[currentMove - 1]);
12490     }
12491
12492     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12493 }
12494
12495
12496 void
12497 EditTagsEvent()
12498 {
12499     char *tags = PGNTags(&gameInfo);
12500     bookUp = FALSE;
12501     EditTagsPopUp(tags, NULL);
12502     free(tags);
12503 }
12504
12505 void
12506 AnalyzeModeEvent()
12507 {
12508     if (appData.noChessProgram || gameMode == AnalyzeMode)
12509       return;
12510
12511     if (gameMode != AnalyzeFile) {
12512         if (!appData.icsEngineAnalyze) {
12513                EditGameEvent();
12514                if (gameMode != EditGame) return;
12515         }
12516         ResurrectChessProgram();
12517         SendToProgram("analyze\n", &first);
12518         first.analyzing = TRUE;
12519         /*first.maybeThinking = TRUE;*/
12520         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12521         EngineOutputPopUp();
12522     }
12523     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12524     pausing = FALSE;
12525     ModeHighlight();
12526     SetGameInfo();
12527
12528     StartAnalysisClock();
12529     GetTimeMark(&lastNodeCountTime);
12530     lastNodeCount = 0;
12531 }
12532
12533 void
12534 AnalyzeFileEvent()
12535 {
12536     if (appData.noChessProgram || gameMode == AnalyzeFile)
12537       return;
12538
12539     if (gameMode != AnalyzeMode) {
12540         EditGameEvent();
12541         if (gameMode != EditGame) return;
12542         ResurrectChessProgram();
12543         SendToProgram("analyze\n", &first);
12544         first.analyzing = TRUE;
12545         /*first.maybeThinking = TRUE;*/
12546         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12547         EngineOutputPopUp();
12548     }
12549     gameMode = AnalyzeFile;
12550     pausing = FALSE;
12551     ModeHighlight();
12552     SetGameInfo();
12553
12554     StartAnalysisClock();
12555     GetTimeMark(&lastNodeCountTime);
12556     lastNodeCount = 0;
12557 }
12558
12559 void
12560 MachineWhiteEvent()
12561 {
12562     char buf[MSG_SIZ];
12563     char *bookHit = NULL;
12564
12565     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12566       return;
12567
12568
12569     if (gameMode == PlayFromGameFile ||
12570         gameMode == TwoMachinesPlay  ||
12571         gameMode == Training         ||
12572         gameMode == AnalyzeMode      ||
12573         gameMode == EndOfGame)
12574         EditGameEvent();
12575
12576     if (gameMode == EditPosition)
12577         EditPositionDone(TRUE);
12578
12579     if (!WhiteOnMove(currentMove)) {
12580         DisplayError(_("It is not White's turn"), 0);
12581         return;
12582     }
12583
12584     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12585       ExitAnalyzeMode();
12586
12587     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12588         gameMode == AnalyzeFile)
12589         TruncateGame();
12590
12591     ResurrectChessProgram();    /* in case it isn't running */
12592     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12593         gameMode = MachinePlaysWhite;
12594         ResetClocks();
12595     } else
12596     gameMode = MachinePlaysWhite;
12597     pausing = FALSE;
12598     ModeHighlight();
12599     SetGameInfo();
12600     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12601     DisplayTitle(buf);
12602     if (first.sendName) {
12603       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12604       SendToProgram(buf, &first);
12605     }
12606     if (first.sendTime) {
12607       if (first.useColors) {
12608         SendToProgram("black\n", &first); /*gnu kludge*/
12609       }
12610       SendTimeRemaining(&first, TRUE);
12611     }
12612     if (first.useColors) {
12613       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12614     }
12615     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12616     SetMachineThinkingEnables();
12617     first.maybeThinking = TRUE;
12618     StartClocks();
12619     firstMove = FALSE;
12620
12621     if (appData.autoFlipView && !flipView) {
12622       flipView = !flipView;
12623       DrawPosition(FALSE, NULL);
12624       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12625     }
12626
12627     if(bookHit) { // [HGM] book: simulate book reply
12628         static char bookMove[MSG_SIZ]; // a bit generous?
12629
12630         programStats.nodes = programStats.depth = programStats.time =
12631         programStats.score = programStats.got_only_move = 0;
12632         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12633
12634         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12635         strcat(bookMove, bookHit);
12636         HandleMachineMove(bookMove, &first);
12637     }
12638 }
12639
12640 void
12641 MachineBlackEvent()
12642 {
12643   char buf[MSG_SIZ];
12644   char *bookHit = NULL;
12645
12646     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12647         return;
12648
12649
12650     if (gameMode == PlayFromGameFile ||
12651         gameMode == TwoMachinesPlay  ||
12652         gameMode == Training         ||
12653         gameMode == AnalyzeMode      ||
12654         gameMode == EndOfGame)
12655         EditGameEvent();
12656
12657     if (gameMode == EditPosition)
12658         EditPositionDone(TRUE);
12659
12660     if (WhiteOnMove(currentMove)) {
12661         DisplayError(_("It is not Black's turn"), 0);
12662         return;
12663     }
12664
12665     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12666       ExitAnalyzeMode();
12667
12668     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12669         gameMode == AnalyzeFile)
12670         TruncateGame();
12671
12672     ResurrectChessProgram();    /* in case it isn't running */
12673     gameMode = MachinePlaysBlack;
12674     pausing = FALSE;
12675     ModeHighlight();
12676     SetGameInfo();
12677     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12678     DisplayTitle(buf);
12679     if (first.sendName) {
12680       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12681       SendToProgram(buf, &first);
12682     }
12683     if (first.sendTime) {
12684       if (first.useColors) {
12685         SendToProgram("white\n", &first); /*gnu kludge*/
12686       }
12687       SendTimeRemaining(&first, FALSE);
12688     }
12689     if (first.useColors) {
12690       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12691     }
12692     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12693     SetMachineThinkingEnables();
12694     first.maybeThinking = TRUE;
12695     StartClocks();
12696
12697     if (appData.autoFlipView && flipView) {
12698       flipView = !flipView;
12699       DrawPosition(FALSE, NULL);
12700       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12701     }
12702     if(bookHit) { // [HGM] book: simulate book reply
12703         static char bookMove[MSG_SIZ]; // a bit generous?
12704
12705         programStats.nodes = programStats.depth = programStats.time =
12706         programStats.score = programStats.got_only_move = 0;
12707         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12708
12709         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12710         strcat(bookMove, bookHit);
12711         HandleMachineMove(bookMove, &first);
12712     }
12713 }
12714
12715
12716 void
12717 DisplayTwoMachinesTitle()
12718 {
12719     char buf[MSG_SIZ];
12720     if (appData.matchGames > 0) {
12721         if(appData.tourneyFile[0]) {
12722           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12723                    gameInfo.white, gameInfo.black,
12724                    nextGame+1, appData.matchGames+1,
12725                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12726         } else 
12727         if (first.twoMachinesColor[0] == 'w') {
12728           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12729                    gameInfo.white, gameInfo.black,
12730                    first.matchWins, second.matchWins,
12731                    matchGame - 1 - (first.matchWins + second.matchWins));
12732         } else {
12733           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12734                    gameInfo.white, gameInfo.black,
12735                    second.matchWins, first.matchWins,
12736                    matchGame - 1 - (first.matchWins + second.matchWins));
12737         }
12738     } else {
12739       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12740     }
12741     DisplayTitle(buf);
12742 }
12743
12744 void
12745 SettingsMenuIfReady()
12746 {
12747   if (second.lastPing != second.lastPong) {
12748     DisplayMessage("", _("Waiting for second chess program"));
12749     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12750     return;
12751   }
12752   ThawUI();
12753   DisplayMessage("", "");
12754   SettingsPopUp(&second);
12755 }
12756
12757 int
12758 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12759 {
12760     char buf[MSG_SIZ];
12761     if (cps->pr == NULL) {
12762         StartChessProgram(cps);
12763         if (cps->protocolVersion == 1) {
12764           retry();
12765         } else {
12766           /* kludge: allow timeout for initial "feature" command */
12767           FreezeUI();
12768           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12769           DisplayMessage("", buf);
12770           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12771         }
12772         return 1;
12773     }
12774     return 0;
12775 }
12776
12777 void
12778 TwoMachinesEvent P((void))
12779 {
12780     int i;
12781     char buf[MSG_SIZ];
12782     ChessProgramState *onmove;
12783     char *bookHit = NULL;
12784     static int stalling = 0;
12785     TimeMark now;
12786     long wait;
12787
12788     if (appData.noChessProgram) return;
12789
12790     switch (gameMode) {
12791       case TwoMachinesPlay:
12792         return;
12793       case MachinePlaysWhite:
12794       case MachinePlaysBlack:
12795         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12796             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12797             return;
12798         }
12799         /* fall through */
12800       case BeginningOfGame:
12801       case PlayFromGameFile:
12802       case EndOfGame:
12803         EditGameEvent();
12804         if (gameMode != EditGame) return;
12805         break;
12806       case EditPosition:
12807         EditPositionDone(TRUE);
12808         break;
12809       case AnalyzeMode:
12810       case AnalyzeFile:
12811         ExitAnalyzeMode();
12812         break;
12813       case EditGame:
12814       default:
12815         break;
12816     }
12817
12818 //    forwardMostMove = currentMove;
12819     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12820
12821     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12822
12823     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12824     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12825       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12826       return;
12827     }
12828     if(!stalling) {
12829       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12830       SendToProgram("force\n", &second);
12831       stalling = 1;
12832       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12833       return;
12834     }
12835     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12836     if(appData.matchPause>10000 || appData.matchPause<10)
12837                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12838     wait = SubtractTimeMarks(&now, &pauseStart);
12839     if(wait < appData.matchPause) {
12840         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12841         return;
12842     }
12843     stalling = 0;
12844     DisplayMessage("", "");
12845     if (startedFromSetupPosition) {
12846         SendBoard(&second, backwardMostMove);
12847     if (appData.debugMode) {
12848         fprintf(debugFP, "Two Machines\n");
12849     }
12850     }
12851     for (i = backwardMostMove; i < forwardMostMove; i++) {
12852         SendMoveToProgram(i, &second);
12853     }
12854
12855     gameMode = TwoMachinesPlay;
12856     pausing = FALSE;
12857     ModeHighlight();
12858     SetGameInfo();
12859     DisplayTwoMachinesTitle();
12860     firstMove = TRUE;
12861     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12862         onmove = &first;
12863     } else {
12864         onmove = &second;
12865     }
12866     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12867     SendToProgram(first.computerString, &first);
12868     if (first.sendName) {
12869       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12870       SendToProgram(buf, &first);
12871     }
12872     SendToProgram(second.computerString, &second);
12873     if (second.sendName) {
12874       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12875       SendToProgram(buf, &second);
12876     }
12877
12878     ResetClocks();
12879     if (!first.sendTime || !second.sendTime) {
12880         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12881         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12882     }
12883     if (onmove->sendTime) {
12884       if (onmove->useColors) {
12885         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12886       }
12887       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12888     }
12889     if (onmove->useColors) {
12890       SendToProgram(onmove->twoMachinesColor, onmove);
12891     }
12892     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12893 //    SendToProgram("go\n", onmove);
12894     onmove->maybeThinking = TRUE;
12895     SetMachineThinkingEnables();
12896
12897     StartClocks();
12898
12899     if(bookHit) { // [HGM] book: simulate book reply
12900         static char bookMove[MSG_SIZ]; // a bit generous?
12901
12902         programStats.nodes = programStats.depth = programStats.time =
12903         programStats.score = programStats.got_only_move = 0;
12904         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12905
12906         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12907         strcat(bookMove, bookHit);
12908         savedMessage = bookMove; // args for deferred call
12909         savedState = onmove;
12910         ScheduleDelayedEvent(DeferredBookMove, 1);
12911     }
12912 }
12913
12914 void
12915 TrainingEvent()
12916 {
12917     if (gameMode == Training) {
12918       SetTrainingModeOff();
12919       gameMode = PlayFromGameFile;
12920       DisplayMessage("", _("Training mode off"));
12921     } else {
12922       gameMode = Training;
12923       animateTraining = appData.animate;
12924
12925       /* make sure we are not already at the end of the game */
12926       if (currentMove < forwardMostMove) {
12927         SetTrainingModeOn();
12928         DisplayMessage("", _("Training mode on"));
12929       } else {
12930         gameMode = PlayFromGameFile;
12931         DisplayError(_("Already at end of game"), 0);
12932       }
12933     }
12934     ModeHighlight();
12935 }
12936
12937 void
12938 IcsClientEvent()
12939 {
12940     if (!appData.icsActive) return;
12941     switch (gameMode) {
12942       case IcsPlayingWhite:
12943       case IcsPlayingBlack:
12944       case IcsObserving:
12945       case IcsIdle:
12946       case BeginningOfGame:
12947       case IcsExamining:
12948         return;
12949
12950       case EditGame:
12951         break;
12952
12953       case EditPosition:
12954         EditPositionDone(TRUE);
12955         break;
12956
12957       case AnalyzeMode:
12958       case AnalyzeFile:
12959         ExitAnalyzeMode();
12960         break;
12961
12962       default:
12963         EditGameEvent();
12964         break;
12965     }
12966
12967     gameMode = IcsIdle;
12968     ModeHighlight();
12969     return;
12970 }
12971
12972
12973 void
12974 EditGameEvent()
12975 {
12976     int i;
12977
12978     switch (gameMode) {
12979       case Training:
12980         SetTrainingModeOff();
12981         break;
12982       case MachinePlaysWhite:
12983       case MachinePlaysBlack:
12984       case BeginningOfGame:
12985         SendToProgram("force\n", &first);
12986         SetUserThinkingEnables();
12987         break;
12988       case PlayFromGameFile:
12989         (void) StopLoadGameTimer();
12990         if (gameFileFP != NULL) {
12991             gameFileFP = NULL;
12992         }
12993         break;
12994       case EditPosition:
12995         EditPositionDone(TRUE);
12996         break;
12997       case AnalyzeMode:
12998       case AnalyzeFile:
12999         ExitAnalyzeMode();
13000         SendToProgram("force\n", &first);
13001         break;
13002       case TwoMachinesPlay:
13003         GameEnds(EndOfFile, NULL, GE_PLAYER);
13004         ResurrectChessProgram();
13005         SetUserThinkingEnables();
13006         break;
13007       case EndOfGame:
13008         ResurrectChessProgram();
13009         break;
13010       case IcsPlayingBlack:
13011       case IcsPlayingWhite:
13012         DisplayError(_("Warning: You are still playing a game"), 0);
13013         break;
13014       case IcsObserving:
13015         DisplayError(_("Warning: You are still observing a game"), 0);
13016         break;
13017       case IcsExamining:
13018         DisplayError(_("Warning: You are still examining a game"), 0);
13019         break;
13020       case IcsIdle:
13021         break;
13022       case EditGame:
13023       default:
13024         return;
13025     }
13026
13027     pausing = FALSE;
13028     StopClocks();
13029     first.offeredDraw = second.offeredDraw = 0;
13030
13031     if (gameMode == PlayFromGameFile) {
13032         whiteTimeRemaining = timeRemaining[0][currentMove];
13033         blackTimeRemaining = timeRemaining[1][currentMove];
13034         DisplayTitle("");
13035     }
13036
13037     if (gameMode == MachinePlaysWhite ||
13038         gameMode == MachinePlaysBlack ||
13039         gameMode == TwoMachinesPlay ||
13040         gameMode == EndOfGame) {
13041         i = forwardMostMove;
13042         while (i > currentMove) {
13043             SendToProgram("undo\n", &first);
13044             i--;
13045         }
13046         whiteTimeRemaining = timeRemaining[0][currentMove];
13047         blackTimeRemaining = timeRemaining[1][currentMove];
13048         DisplayBothClocks();
13049         if (whiteFlag || blackFlag) {
13050             whiteFlag = blackFlag = 0;
13051         }
13052         DisplayTitle("");
13053     }
13054
13055     gameMode = EditGame;
13056     ModeHighlight();
13057     SetGameInfo();
13058 }
13059
13060
13061 void
13062 EditPositionEvent()
13063 {
13064     if (gameMode == EditPosition) {
13065         EditGameEvent();
13066         return;
13067     }
13068
13069     EditGameEvent();
13070     if (gameMode != EditGame) return;
13071
13072     gameMode = EditPosition;
13073     ModeHighlight();
13074     SetGameInfo();
13075     if (currentMove > 0)
13076       CopyBoard(boards[0], boards[currentMove]);
13077
13078     blackPlaysFirst = !WhiteOnMove(currentMove);
13079     ResetClocks();
13080     currentMove = forwardMostMove = backwardMostMove = 0;
13081     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13082     DisplayMove(-1);
13083 }
13084
13085 void
13086 ExitAnalyzeMode()
13087 {
13088     /* [DM] icsEngineAnalyze - possible call from other functions */
13089     if (appData.icsEngineAnalyze) {
13090         appData.icsEngineAnalyze = FALSE;
13091
13092         DisplayMessage("",_("Close ICS engine analyze..."));
13093     }
13094     if (first.analysisSupport && first.analyzing) {
13095       SendToProgram("exit\n", &first);
13096       first.analyzing = FALSE;
13097     }
13098     thinkOutput[0] = NULLCHAR;
13099 }
13100
13101 void
13102 EditPositionDone(Boolean fakeRights)
13103 {
13104     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13105
13106     startedFromSetupPosition = TRUE;
13107     InitChessProgram(&first, FALSE);
13108     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13109       boards[0][EP_STATUS] = EP_NONE;
13110       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13111     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13112         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13113         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13114       } else boards[0][CASTLING][2] = NoRights;
13115     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13116         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13117         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13118       } else boards[0][CASTLING][5] = NoRights;
13119     }
13120     SendToProgram("force\n", &first);
13121     if (blackPlaysFirst) {
13122         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13123         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13124         currentMove = forwardMostMove = backwardMostMove = 1;
13125         CopyBoard(boards[1], boards[0]);
13126     } else {
13127         currentMove = forwardMostMove = backwardMostMove = 0;
13128     }
13129     SendBoard(&first, forwardMostMove);
13130     if (appData.debugMode) {
13131         fprintf(debugFP, "EditPosDone\n");
13132     }
13133     DisplayTitle("");
13134     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13135     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13136     gameMode = EditGame;
13137     ModeHighlight();
13138     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13139     ClearHighlights(); /* [AS] */
13140 }
13141
13142 /* Pause for `ms' milliseconds */
13143 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13144 void
13145 TimeDelay(ms)
13146      long ms;
13147 {
13148     TimeMark m1, m2;
13149
13150     GetTimeMark(&m1);
13151     do {
13152         GetTimeMark(&m2);
13153     } while (SubtractTimeMarks(&m2, &m1) < ms);
13154 }
13155
13156 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13157 void
13158 SendMultiLineToICS(buf)
13159      char *buf;
13160 {
13161     char temp[MSG_SIZ+1], *p;
13162     int len;
13163
13164     len = strlen(buf);
13165     if (len > MSG_SIZ)
13166       len = MSG_SIZ;
13167
13168     strncpy(temp, buf, len);
13169     temp[len] = 0;
13170
13171     p = temp;
13172     while (*p) {
13173         if (*p == '\n' || *p == '\r')
13174           *p = ' ';
13175         ++p;
13176     }
13177
13178     strcat(temp, "\n");
13179     SendToICS(temp);
13180     SendToPlayer(temp, strlen(temp));
13181 }
13182
13183 void
13184 SetWhiteToPlayEvent()
13185 {
13186     if (gameMode == EditPosition) {
13187         blackPlaysFirst = FALSE;
13188         DisplayBothClocks();    /* works because currentMove is 0 */
13189     } else if (gameMode == IcsExamining) {
13190         SendToICS(ics_prefix);
13191         SendToICS("tomove white\n");
13192     }
13193 }
13194
13195 void
13196 SetBlackToPlayEvent()
13197 {
13198     if (gameMode == EditPosition) {
13199         blackPlaysFirst = TRUE;
13200         currentMove = 1;        /* kludge */
13201         DisplayBothClocks();
13202         currentMove = 0;
13203     } else if (gameMode == IcsExamining) {
13204         SendToICS(ics_prefix);
13205         SendToICS("tomove black\n");
13206     }
13207 }
13208
13209 void
13210 EditPositionMenuEvent(selection, x, y)
13211      ChessSquare selection;
13212      int x, y;
13213 {
13214     char buf[MSG_SIZ];
13215     ChessSquare piece = boards[0][y][x];
13216
13217     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13218
13219     switch (selection) {
13220       case ClearBoard:
13221         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13222             SendToICS(ics_prefix);
13223             SendToICS("bsetup clear\n");
13224         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13225             SendToICS(ics_prefix);
13226             SendToICS("clearboard\n");
13227         } else {
13228             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13229                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13230                 for (y = 0; y < BOARD_HEIGHT; y++) {
13231                     if (gameMode == IcsExamining) {
13232                         if (boards[currentMove][y][x] != EmptySquare) {
13233                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13234                                     AAA + x, ONE + y);
13235                             SendToICS(buf);
13236                         }
13237                     } else {
13238                         boards[0][y][x] = p;
13239                     }
13240                 }
13241             }
13242         }
13243         if (gameMode == EditPosition) {
13244             DrawPosition(FALSE, boards[0]);
13245         }
13246         break;
13247
13248       case WhitePlay:
13249         SetWhiteToPlayEvent();
13250         break;
13251
13252       case BlackPlay:
13253         SetBlackToPlayEvent();
13254         break;
13255
13256       case EmptySquare:
13257         if (gameMode == IcsExamining) {
13258             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13259             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13260             SendToICS(buf);
13261         } else {
13262             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13263                 if(x == BOARD_LEFT-2) {
13264                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13265                     boards[0][y][1] = 0;
13266                 } else
13267                 if(x == BOARD_RGHT+1) {
13268                     if(y >= gameInfo.holdingsSize) break;
13269                     boards[0][y][BOARD_WIDTH-2] = 0;
13270                 } else break;
13271             }
13272             boards[0][y][x] = EmptySquare;
13273             DrawPosition(FALSE, boards[0]);
13274         }
13275         break;
13276
13277       case PromotePiece:
13278         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13279            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13280             selection = (ChessSquare) (PROMOTED piece);
13281         } else if(piece == EmptySquare) selection = WhiteSilver;
13282         else selection = (ChessSquare)((int)piece - 1);
13283         goto defaultlabel;
13284
13285       case DemotePiece:
13286         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13287            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13288             selection = (ChessSquare) (DEMOTED piece);
13289         } else if(piece == EmptySquare) selection = BlackSilver;
13290         else selection = (ChessSquare)((int)piece + 1);
13291         goto defaultlabel;
13292
13293       case WhiteQueen:
13294       case BlackQueen:
13295         if(gameInfo.variant == VariantShatranj ||
13296            gameInfo.variant == VariantXiangqi  ||
13297            gameInfo.variant == VariantCourier  ||
13298            gameInfo.variant == VariantMakruk     )
13299             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13300         goto defaultlabel;
13301
13302       case WhiteKing:
13303       case BlackKing:
13304         if(gameInfo.variant == VariantXiangqi)
13305             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13306         if(gameInfo.variant == VariantKnightmate)
13307             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13308       default:
13309         defaultlabel:
13310         if (gameMode == IcsExamining) {
13311             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13312             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13313                      PieceToChar(selection), AAA + x, ONE + y);
13314             SendToICS(buf);
13315         } else {
13316             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13317                 int n;
13318                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13319                     n = PieceToNumber(selection - BlackPawn);
13320                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13321                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13322                     boards[0][BOARD_HEIGHT-1-n][1]++;
13323                 } else
13324                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13325                     n = PieceToNumber(selection);
13326                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13327                     boards[0][n][BOARD_WIDTH-1] = selection;
13328                     boards[0][n][BOARD_WIDTH-2]++;
13329                 }
13330             } else
13331             boards[0][y][x] = selection;
13332             DrawPosition(TRUE, boards[0]);
13333         }
13334         break;
13335     }
13336 }
13337
13338
13339 void
13340 DropMenuEvent(selection, x, y)
13341      ChessSquare selection;
13342      int x, y;
13343 {
13344     ChessMove moveType;
13345
13346     switch (gameMode) {
13347       case IcsPlayingWhite:
13348       case MachinePlaysBlack:
13349         if (!WhiteOnMove(currentMove)) {
13350             DisplayMoveError(_("It is Black's turn"));
13351             return;
13352         }
13353         moveType = WhiteDrop;
13354         break;
13355       case IcsPlayingBlack:
13356       case MachinePlaysWhite:
13357         if (WhiteOnMove(currentMove)) {
13358             DisplayMoveError(_("It is White's turn"));
13359             return;
13360         }
13361         moveType = BlackDrop;
13362         break;
13363       case EditGame:
13364         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13365         break;
13366       default:
13367         return;
13368     }
13369
13370     if (moveType == BlackDrop && selection < BlackPawn) {
13371       selection = (ChessSquare) ((int) selection
13372                                  + (int) BlackPawn - (int) WhitePawn);
13373     }
13374     if (boards[currentMove][y][x] != EmptySquare) {
13375         DisplayMoveError(_("That square is occupied"));
13376         return;
13377     }
13378
13379     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13380 }
13381
13382 void
13383 AcceptEvent()
13384 {
13385     /* Accept a pending offer of any kind from opponent */
13386
13387     if (appData.icsActive) {
13388         SendToICS(ics_prefix);
13389         SendToICS("accept\n");
13390     } else if (cmailMsgLoaded) {
13391         if (currentMove == cmailOldMove &&
13392             commentList[cmailOldMove] != NULL &&
13393             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13394                    "Black offers a draw" : "White offers a draw")) {
13395             TruncateGame();
13396             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13397             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13398         } else {
13399             DisplayError(_("There is no pending offer on this move"), 0);
13400             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13401         }
13402     } else {
13403         /* Not used for offers from chess program */
13404     }
13405 }
13406
13407 void
13408 DeclineEvent()
13409 {
13410     /* Decline a pending offer of any kind from opponent */
13411
13412     if (appData.icsActive) {
13413         SendToICS(ics_prefix);
13414         SendToICS("decline\n");
13415     } else if (cmailMsgLoaded) {
13416         if (currentMove == cmailOldMove &&
13417             commentList[cmailOldMove] != NULL &&
13418             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13419                    "Black offers a draw" : "White offers a draw")) {
13420 #ifdef NOTDEF
13421             AppendComment(cmailOldMove, "Draw declined", TRUE);
13422             DisplayComment(cmailOldMove - 1, "Draw declined");
13423 #endif /*NOTDEF*/
13424         } else {
13425             DisplayError(_("There is no pending offer on this move"), 0);
13426         }
13427     } else {
13428         /* Not used for offers from chess program */
13429     }
13430 }
13431
13432 void
13433 RematchEvent()
13434 {
13435     /* Issue ICS rematch command */
13436     if (appData.icsActive) {
13437         SendToICS(ics_prefix);
13438         SendToICS("rematch\n");
13439     }
13440 }
13441
13442 void
13443 CallFlagEvent()
13444 {
13445     /* Call your opponent's flag (claim a win on time) */
13446     if (appData.icsActive) {
13447         SendToICS(ics_prefix);
13448         SendToICS("flag\n");
13449     } else {
13450         switch (gameMode) {
13451           default:
13452             return;
13453           case MachinePlaysWhite:
13454             if (whiteFlag) {
13455                 if (blackFlag)
13456                   GameEnds(GameIsDrawn, "Both players ran out of time",
13457                            GE_PLAYER);
13458                 else
13459                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13460             } else {
13461                 DisplayError(_("Your opponent is not out of time"), 0);
13462             }
13463             break;
13464           case MachinePlaysBlack:
13465             if (blackFlag) {
13466                 if (whiteFlag)
13467                   GameEnds(GameIsDrawn, "Both players ran out of time",
13468                            GE_PLAYER);
13469                 else
13470                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13471             } else {
13472                 DisplayError(_("Your opponent is not out of time"), 0);
13473             }
13474             break;
13475         }
13476     }
13477 }
13478
13479 void
13480 ClockClick(int which)
13481 {       // [HGM] code moved to back-end from winboard.c
13482         if(which) { // black clock
13483           if (gameMode == EditPosition || gameMode == IcsExamining) {
13484             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13485             SetBlackToPlayEvent();
13486           } else if (gameMode == EditGame || shiftKey) {
13487             AdjustClock(which, -1);
13488           } else if (gameMode == IcsPlayingWhite ||
13489                      gameMode == MachinePlaysBlack) {
13490             CallFlagEvent();
13491           }
13492         } else { // white clock
13493           if (gameMode == EditPosition || gameMode == IcsExamining) {
13494             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13495             SetWhiteToPlayEvent();
13496           } else if (gameMode == EditGame || shiftKey) {
13497             AdjustClock(which, -1);
13498           } else if (gameMode == IcsPlayingBlack ||
13499                    gameMode == MachinePlaysWhite) {
13500             CallFlagEvent();
13501           }
13502         }
13503 }
13504
13505 void
13506 DrawEvent()
13507 {
13508     /* Offer draw or accept pending draw offer from opponent */
13509
13510     if (appData.icsActive) {
13511         /* Note: tournament rules require draw offers to be
13512            made after you make your move but before you punch
13513            your clock.  Currently ICS doesn't let you do that;
13514            instead, you immediately punch your clock after making
13515            a move, but you can offer a draw at any time. */
13516
13517         SendToICS(ics_prefix);
13518         SendToICS("draw\n");
13519         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13520     } else if (cmailMsgLoaded) {
13521         if (currentMove == cmailOldMove &&
13522             commentList[cmailOldMove] != NULL &&
13523             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13524                    "Black offers a draw" : "White offers a draw")) {
13525             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13526             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13527         } else if (currentMove == cmailOldMove + 1) {
13528             char *offer = WhiteOnMove(cmailOldMove) ?
13529               "White offers a draw" : "Black offers a draw";
13530             AppendComment(currentMove, offer, TRUE);
13531             DisplayComment(currentMove - 1, offer);
13532             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13533         } else {
13534             DisplayError(_("You must make your move before offering a draw"), 0);
13535             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13536         }
13537     } else if (first.offeredDraw) {
13538         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13539     } else {
13540         if (first.sendDrawOffers) {
13541             SendToProgram("draw\n", &first);
13542             userOfferedDraw = TRUE;
13543         }
13544     }
13545 }
13546
13547 void
13548 AdjournEvent()
13549 {
13550     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13551
13552     if (appData.icsActive) {
13553         SendToICS(ics_prefix);
13554         SendToICS("adjourn\n");
13555     } else {
13556         /* Currently GNU Chess doesn't offer or accept Adjourns */
13557     }
13558 }
13559
13560
13561 void
13562 AbortEvent()
13563 {
13564     /* Offer Abort or accept pending Abort offer from opponent */
13565
13566     if (appData.icsActive) {
13567         SendToICS(ics_prefix);
13568         SendToICS("abort\n");
13569     } else {
13570         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13571     }
13572 }
13573
13574 void
13575 ResignEvent()
13576 {
13577     /* Resign.  You can do this even if it's not your turn. */
13578
13579     if (appData.icsActive) {
13580         SendToICS(ics_prefix);
13581         SendToICS("resign\n");
13582     } else {
13583         switch (gameMode) {
13584           case MachinePlaysWhite:
13585             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13586             break;
13587           case MachinePlaysBlack:
13588             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13589             break;
13590           case EditGame:
13591             if (cmailMsgLoaded) {
13592                 TruncateGame();
13593                 if (WhiteOnMove(cmailOldMove)) {
13594                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13595                 } else {
13596                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13597                 }
13598                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13599             }
13600             break;
13601           default:
13602             break;
13603         }
13604     }
13605 }
13606
13607
13608 void
13609 StopObservingEvent()
13610 {
13611     /* Stop observing current games */
13612     SendToICS(ics_prefix);
13613     SendToICS("unobserve\n");
13614 }
13615
13616 void
13617 StopExaminingEvent()
13618 {
13619     /* Stop observing current game */
13620     SendToICS(ics_prefix);
13621     SendToICS("unexamine\n");
13622 }
13623
13624 void
13625 ForwardInner(target)
13626      int target;
13627 {
13628     int limit;
13629
13630     if (appData.debugMode)
13631         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13632                 target, currentMove, forwardMostMove);
13633
13634     if (gameMode == EditPosition)
13635       return;
13636
13637     if (gameMode == PlayFromGameFile && !pausing)
13638       PauseEvent();
13639
13640     if (gameMode == IcsExamining && pausing)
13641       limit = pauseExamForwardMostMove;
13642     else
13643       limit = forwardMostMove;
13644
13645     if (target > limit) target = limit;
13646
13647     if (target > 0 && moveList[target - 1][0]) {
13648         int fromX, fromY, toX, toY;
13649         toX = moveList[target - 1][2] - AAA;
13650         toY = moveList[target - 1][3] - ONE;
13651         if (moveList[target - 1][1] == '@') {
13652             if (appData.highlightLastMove) {
13653                 SetHighlights(-1, -1, toX, toY);
13654             }
13655         } else {
13656             fromX = moveList[target - 1][0] - AAA;
13657             fromY = moveList[target - 1][1] - ONE;
13658             if (target == currentMove + 1) {
13659                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13660             }
13661             if (appData.highlightLastMove) {
13662                 SetHighlights(fromX, fromY, toX, toY);
13663             }
13664         }
13665     }
13666     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13667         gameMode == Training || gameMode == PlayFromGameFile ||
13668         gameMode == AnalyzeFile) {
13669         while (currentMove < target) {
13670             SendMoveToProgram(currentMove++, &first);
13671         }
13672     } else {
13673         currentMove = target;
13674     }
13675
13676     if (gameMode == EditGame || gameMode == EndOfGame) {
13677         whiteTimeRemaining = timeRemaining[0][currentMove];
13678         blackTimeRemaining = timeRemaining[1][currentMove];
13679     }
13680     DisplayBothClocks();
13681     DisplayMove(currentMove - 1);
13682     DrawPosition(FALSE, boards[currentMove]);
13683     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13684     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13685         DisplayComment(currentMove - 1, commentList[currentMove]);
13686     }
13687     DisplayBook(currentMove);
13688 }
13689
13690
13691 void
13692 ForwardEvent()
13693 {
13694     if (gameMode == IcsExamining && !pausing) {
13695         SendToICS(ics_prefix);
13696         SendToICS("forward\n");
13697     } else {
13698         ForwardInner(currentMove + 1);
13699     }
13700 }
13701
13702 void
13703 ToEndEvent()
13704 {
13705     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13706         /* to optimze, we temporarily turn off analysis mode while we feed
13707          * the remaining moves to the engine. Otherwise we get analysis output
13708          * after each move.
13709          */
13710         if (first.analysisSupport) {
13711           SendToProgram("exit\nforce\n", &first);
13712           first.analyzing = FALSE;
13713         }
13714     }
13715
13716     if (gameMode == IcsExamining && !pausing) {
13717         SendToICS(ics_prefix);
13718         SendToICS("forward 999999\n");
13719     } else {
13720         ForwardInner(forwardMostMove);
13721     }
13722
13723     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13724         /* we have fed all the moves, so reactivate analysis mode */
13725         SendToProgram("analyze\n", &first);
13726         first.analyzing = TRUE;
13727         /*first.maybeThinking = TRUE;*/
13728         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13729     }
13730 }
13731
13732 void
13733 BackwardInner(target)
13734      int target;
13735 {
13736     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13737
13738     if (appData.debugMode)
13739         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13740                 target, currentMove, forwardMostMove);
13741
13742     if (gameMode == EditPosition) return;
13743     if (currentMove <= backwardMostMove) {
13744         ClearHighlights();
13745         DrawPosition(full_redraw, boards[currentMove]);
13746         return;
13747     }
13748     if (gameMode == PlayFromGameFile && !pausing)
13749       PauseEvent();
13750
13751     if (moveList[target][0]) {
13752         int fromX, fromY, toX, toY;
13753         toX = moveList[target][2] - AAA;
13754         toY = moveList[target][3] - ONE;
13755         if (moveList[target][1] == '@') {
13756             if (appData.highlightLastMove) {
13757                 SetHighlights(-1, -1, toX, toY);
13758             }
13759         } else {
13760             fromX = moveList[target][0] - AAA;
13761             fromY = moveList[target][1] - ONE;
13762             if (target == currentMove - 1) {
13763                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13764             }
13765             if (appData.highlightLastMove) {
13766                 SetHighlights(fromX, fromY, toX, toY);
13767             }
13768         }
13769     }
13770     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13771         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13772         while (currentMove > target) {
13773             SendToProgram("undo\n", &first);
13774             currentMove--;
13775         }
13776     } else {
13777         currentMove = target;
13778     }
13779
13780     if (gameMode == EditGame || gameMode == EndOfGame) {
13781         whiteTimeRemaining = timeRemaining[0][currentMove];
13782         blackTimeRemaining = timeRemaining[1][currentMove];
13783     }
13784     DisplayBothClocks();
13785     DisplayMove(currentMove - 1);
13786     DrawPosition(full_redraw, boards[currentMove]);
13787     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13788     // [HGM] PV info: routine tests if comment empty
13789     DisplayComment(currentMove - 1, commentList[currentMove]);
13790     DisplayBook(currentMove);
13791 }
13792
13793 void
13794 BackwardEvent()
13795 {
13796     if (gameMode == IcsExamining && !pausing) {
13797         SendToICS(ics_prefix);
13798         SendToICS("backward\n");
13799     } else {
13800         BackwardInner(currentMove - 1);
13801     }
13802 }
13803
13804 void
13805 ToStartEvent()
13806 {
13807     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13808         /* to optimize, we temporarily turn off analysis mode while we undo
13809          * all the moves. Otherwise we get analysis output after each undo.
13810          */
13811         if (first.analysisSupport) {
13812           SendToProgram("exit\nforce\n", &first);
13813           first.analyzing = FALSE;
13814         }
13815     }
13816
13817     if (gameMode == IcsExamining && !pausing) {
13818         SendToICS(ics_prefix);
13819         SendToICS("backward 999999\n");
13820     } else {
13821         BackwardInner(backwardMostMove);
13822     }
13823
13824     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13825         /* we have fed all the moves, so reactivate analysis mode */
13826         SendToProgram("analyze\n", &first);
13827         first.analyzing = TRUE;
13828         /*first.maybeThinking = TRUE;*/
13829         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13830     }
13831 }
13832
13833 void
13834 ToNrEvent(int to)
13835 {
13836   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13837   if (to >= forwardMostMove) to = forwardMostMove;
13838   if (to <= backwardMostMove) to = backwardMostMove;
13839   if (to < currentMove) {
13840     BackwardInner(to);
13841   } else {
13842     ForwardInner(to);
13843   }
13844 }
13845
13846 void
13847 RevertEvent(Boolean annotate)
13848 {
13849     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13850         return;
13851     }
13852     if (gameMode != IcsExamining) {
13853         DisplayError(_("You are not examining a game"), 0);
13854         return;
13855     }
13856     if (pausing) {
13857         DisplayError(_("You can't revert while pausing"), 0);
13858         return;
13859     }
13860     SendToICS(ics_prefix);
13861     SendToICS("revert\n");
13862 }
13863
13864 void
13865 RetractMoveEvent()
13866 {
13867     switch (gameMode) {
13868       case MachinePlaysWhite:
13869       case MachinePlaysBlack:
13870         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13871             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13872             return;
13873         }
13874         if (forwardMostMove < 2) return;
13875         currentMove = forwardMostMove = forwardMostMove - 2;
13876         whiteTimeRemaining = timeRemaining[0][currentMove];
13877         blackTimeRemaining = timeRemaining[1][currentMove];
13878         DisplayBothClocks();
13879         DisplayMove(currentMove - 1);
13880         ClearHighlights();/*!! could figure this out*/
13881         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13882         SendToProgram("remove\n", &first);
13883         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13884         break;
13885
13886       case BeginningOfGame:
13887       default:
13888         break;
13889
13890       case IcsPlayingWhite:
13891       case IcsPlayingBlack:
13892         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13893             SendToICS(ics_prefix);
13894             SendToICS("takeback 2\n");
13895         } else {
13896             SendToICS(ics_prefix);
13897             SendToICS("takeback 1\n");
13898         }
13899         break;
13900     }
13901 }
13902
13903 void
13904 MoveNowEvent()
13905 {
13906     ChessProgramState *cps;
13907
13908     switch (gameMode) {
13909       case MachinePlaysWhite:
13910         if (!WhiteOnMove(forwardMostMove)) {
13911             DisplayError(_("It is your turn"), 0);
13912             return;
13913         }
13914         cps = &first;
13915         break;
13916       case MachinePlaysBlack:
13917         if (WhiteOnMove(forwardMostMove)) {
13918             DisplayError(_("It is your turn"), 0);
13919             return;
13920         }
13921         cps = &first;
13922         break;
13923       case TwoMachinesPlay:
13924         if (WhiteOnMove(forwardMostMove) ==
13925             (first.twoMachinesColor[0] == 'w')) {
13926             cps = &first;
13927         } else {
13928             cps = &second;
13929         }
13930         break;
13931       case BeginningOfGame:
13932       default:
13933         return;
13934     }
13935     SendToProgram("?\n", cps);
13936 }
13937
13938 void
13939 TruncateGameEvent()
13940 {
13941     EditGameEvent();
13942     if (gameMode != EditGame) return;
13943     TruncateGame();
13944 }
13945
13946 void
13947 TruncateGame()
13948 {
13949     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13950     if (forwardMostMove > currentMove) {
13951         if (gameInfo.resultDetails != NULL) {
13952             free(gameInfo.resultDetails);
13953             gameInfo.resultDetails = NULL;
13954             gameInfo.result = GameUnfinished;
13955         }
13956         forwardMostMove = currentMove;
13957         HistorySet(parseList, backwardMostMove, forwardMostMove,
13958                    currentMove-1);
13959     }
13960 }
13961
13962 void
13963 HintEvent()
13964 {
13965     if (appData.noChessProgram) return;
13966     switch (gameMode) {
13967       case MachinePlaysWhite:
13968         if (WhiteOnMove(forwardMostMove)) {
13969             DisplayError(_("Wait until your turn"), 0);
13970             return;
13971         }
13972         break;
13973       case BeginningOfGame:
13974       case MachinePlaysBlack:
13975         if (!WhiteOnMove(forwardMostMove)) {
13976             DisplayError(_("Wait until your turn"), 0);
13977             return;
13978         }
13979         break;
13980       default:
13981         DisplayError(_("No hint available"), 0);
13982         return;
13983     }
13984     SendToProgram("hint\n", &first);
13985     hintRequested = TRUE;
13986 }
13987
13988 void
13989 BookEvent()
13990 {
13991     if (appData.noChessProgram) return;
13992     switch (gameMode) {
13993       case MachinePlaysWhite:
13994         if (WhiteOnMove(forwardMostMove)) {
13995             DisplayError(_("Wait until your turn"), 0);
13996             return;
13997         }
13998         break;
13999       case BeginningOfGame:
14000       case MachinePlaysBlack:
14001         if (!WhiteOnMove(forwardMostMove)) {
14002             DisplayError(_("Wait until your turn"), 0);
14003             return;
14004         }
14005         break;
14006       case EditPosition:
14007         EditPositionDone(TRUE);
14008         break;
14009       case TwoMachinesPlay:
14010         return;
14011       default:
14012         break;
14013     }
14014     SendToProgram("bk\n", &first);
14015     bookOutput[0] = NULLCHAR;
14016     bookRequested = TRUE;
14017 }
14018
14019 void
14020 AboutGameEvent()
14021 {
14022     char *tags = PGNTags(&gameInfo);
14023     TagsPopUp(tags, CmailMsg());
14024     free(tags);
14025 }
14026
14027 /* end button procedures */
14028
14029 void
14030 PrintPosition(fp, move)
14031      FILE *fp;
14032      int move;
14033 {
14034     int i, j;
14035
14036     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14037         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14038             char c = PieceToChar(boards[move][i][j]);
14039             fputc(c == 'x' ? '.' : c, fp);
14040             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14041         }
14042     }
14043     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14044       fprintf(fp, "white to play\n");
14045     else
14046       fprintf(fp, "black to play\n");
14047 }
14048
14049 void
14050 PrintOpponents(fp)
14051      FILE *fp;
14052 {
14053     if (gameInfo.white != NULL) {
14054         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14055     } else {
14056         fprintf(fp, "\n");
14057     }
14058 }
14059
14060 /* Find last component of program's own name, using some heuristics */
14061 void
14062 TidyProgramName(prog, host, buf)
14063      char *prog, *host, buf[MSG_SIZ];
14064 {
14065     char *p, *q;
14066     int local = (strcmp(host, "localhost") == 0);
14067     while (!local && (p = strchr(prog, ';')) != NULL) {
14068         p++;
14069         while (*p == ' ') p++;
14070         prog = p;
14071     }
14072     if (*prog == '"' || *prog == '\'') {
14073         q = strchr(prog + 1, *prog);
14074     } else {
14075         q = strchr(prog, ' ');
14076     }
14077     if (q == NULL) q = prog + strlen(prog);
14078     p = q;
14079     while (p >= prog && *p != '/' && *p != '\\') p--;
14080     p++;
14081     if(p == prog && *p == '"') p++;
14082     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14083     memcpy(buf, p, q - p);
14084     buf[q - p] = NULLCHAR;
14085     if (!local) {
14086         strcat(buf, "@");
14087         strcat(buf, host);
14088     }
14089 }
14090
14091 char *
14092 TimeControlTagValue()
14093 {
14094     char buf[MSG_SIZ];
14095     if (!appData.clockMode) {
14096       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14097     } else if (movesPerSession > 0) {
14098       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14099     } else if (timeIncrement == 0) {
14100       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14101     } else {
14102       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14103     }
14104     return StrSave(buf);
14105 }
14106
14107 void
14108 SetGameInfo()
14109 {
14110     /* This routine is used only for certain modes */
14111     VariantClass v = gameInfo.variant;
14112     ChessMove r = GameUnfinished;
14113     char *p = NULL;
14114
14115     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14116         r = gameInfo.result;
14117         p = gameInfo.resultDetails;
14118         gameInfo.resultDetails = NULL;
14119     }
14120     ClearGameInfo(&gameInfo);
14121     gameInfo.variant = v;
14122
14123     switch (gameMode) {
14124       case MachinePlaysWhite:
14125         gameInfo.event = StrSave( appData.pgnEventHeader );
14126         gameInfo.site = StrSave(HostName());
14127         gameInfo.date = PGNDate();
14128         gameInfo.round = StrSave("-");
14129         gameInfo.white = StrSave(first.tidy);
14130         gameInfo.black = StrSave(UserName());
14131         gameInfo.timeControl = TimeControlTagValue();
14132         break;
14133
14134       case MachinePlaysBlack:
14135         gameInfo.event = StrSave( appData.pgnEventHeader );
14136         gameInfo.site = StrSave(HostName());
14137         gameInfo.date = PGNDate();
14138         gameInfo.round = StrSave("-");
14139         gameInfo.white = StrSave(UserName());
14140         gameInfo.black = StrSave(first.tidy);
14141         gameInfo.timeControl = TimeControlTagValue();
14142         break;
14143
14144       case TwoMachinesPlay:
14145         gameInfo.event = StrSave( appData.pgnEventHeader );
14146         gameInfo.site = StrSave(HostName());
14147         gameInfo.date = PGNDate();
14148         if (roundNr > 0) {
14149             char buf[MSG_SIZ];
14150             snprintf(buf, MSG_SIZ, "%d", roundNr);
14151             gameInfo.round = StrSave(buf);
14152         } else {
14153             gameInfo.round = StrSave("-");
14154         }
14155         if (first.twoMachinesColor[0] == 'w') {
14156             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14157             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14158         } else {
14159             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14160             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14161         }
14162         gameInfo.timeControl = TimeControlTagValue();
14163         break;
14164
14165       case EditGame:
14166         gameInfo.event = StrSave("Edited game");
14167         gameInfo.site = StrSave(HostName());
14168         gameInfo.date = PGNDate();
14169         gameInfo.round = StrSave("-");
14170         gameInfo.white = StrSave("-");
14171         gameInfo.black = StrSave("-");
14172         gameInfo.result = r;
14173         gameInfo.resultDetails = p;
14174         break;
14175
14176       case EditPosition:
14177         gameInfo.event = StrSave("Edited position");
14178         gameInfo.site = StrSave(HostName());
14179         gameInfo.date = PGNDate();
14180         gameInfo.round = StrSave("-");
14181         gameInfo.white = StrSave("-");
14182         gameInfo.black = StrSave("-");
14183         break;
14184
14185       case IcsPlayingWhite:
14186       case IcsPlayingBlack:
14187       case IcsObserving:
14188       case IcsExamining:
14189         break;
14190
14191       case PlayFromGameFile:
14192         gameInfo.event = StrSave("Game from non-PGN file");
14193         gameInfo.site = StrSave(HostName());
14194         gameInfo.date = PGNDate();
14195         gameInfo.round = StrSave("-");
14196         gameInfo.white = StrSave("?");
14197         gameInfo.black = StrSave("?");
14198         break;
14199
14200       default:
14201         break;
14202     }
14203 }
14204
14205 void
14206 ReplaceComment(index, text)
14207      int index;
14208      char *text;
14209 {
14210     int len;
14211     char *p;
14212     float score;
14213
14214     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14215        pvInfoList[index-1].depth == len &&
14216        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14217        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14218     while (*text == '\n') text++;
14219     len = strlen(text);
14220     while (len > 0 && text[len - 1] == '\n') len--;
14221
14222     if (commentList[index] != NULL)
14223       free(commentList[index]);
14224
14225     if (len == 0) {
14226         commentList[index] = NULL;
14227         return;
14228     }
14229   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14230       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14231       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14232     commentList[index] = (char *) malloc(len + 2);
14233     strncpy(commentList[index], text, len);
14234     commentList[index][len] = '\n';
14235     commentList[index][len + 1] = NULLCHAR;
14236   } else {
14237     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14238     char *p;
14239     commentList[index] = (char *) malloc(len + 7);
14240     safeStrCpy(commentList[index], "{\n", 3);
14241     safeStrCpy(commentList[index]+2, text, len+1);
14242     commentList[index][len+2] = NULLCHAR;
14243     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14244     strcat(commentList[index], "\n}\n");
14245   }
14246 }
14247
14248 void
14249 CrushCRs(text)
14250      char *text;
14251 {
14252   char *p = text;
14253   char *q = text;
14254   char ch;
14255
14256   do {
14257     ch = *p++;
14258     if (ch == '\r') continue;
14259     *q++ = ch;
14260   } while (ch != '\0');
14261 }
14262
14263 void
14264 AppendComment(index, text, addBraces)
14265      int index;
14266      char *text;
14267      Boolean addBraces; // [HGM] braces: tells if we should add {}
14268 {
14269     int oldlen, len;
14270     char *old;
14271
14272 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14273     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14274
14275     CrushCRs(text);
14276     while (*text == '\n') text++;
14277     len = strlen(text);
14278     while (len > 0 && text[len - 1] == '\n') len--;
14279
14280     if (len == 0) return;
14281
14282     if (commentList[index] != NULL) {
14283         old = commentList[index];
14284         oldlen = strlen(old);
14285         while(commentList[index][oldlen-1] ==  '\n')
14286           commentList[index][--oldlen] = NULLCHAR;
14287         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14288         safeStrCpy(commentList[index], old, oldlen + len + 6);
14289         free(old);
14290         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14291         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14292           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14293           while (*text == '\n') { text++; len--; }
14294           commentList[index][--oldlen] = NULLCHAR;
14295       }
14296         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14297         else          strcat(commentList[index], "\n");
14298         strcat(commentList[index], text);
14299         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14300         else          strcat(commentList[index], "\n");
14301     } else {
14302         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14303         if(addBraces)
14304           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14305         else commentList[index][0] = NULLCHAR;
14306         strcat(commentList[index], text);
14307         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14308         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14309     }
14310 }
14311
14312 static char * FindStr( char * text, char * sub_text )
14313 {
14314     char * result = strstr( text, sub_text );
14315
14316     if( result != NULL ) {
14317         result += strlen( sub_text );
14318     }
14319
14320     return result;
14321 }
14322
14323 /* [AS] Try to extract PV info from PGN comment */
14324 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14325 char *GetInfoFromComment( int index, char * text )
14326 {
14327     char * sep = text, *p;
14328
14329     if( text != NULL && index > 0 ) {
14330         int score = 0;
14331         int depth = 0;
14332         int time = -1, sec = 0, deci;
14333         char * s_eval = FindStr( text, "[%eval " );
14334         char * s_emt = FindStr( text, "[%emt " );
14335
14336         if( s_eval != NULL || s_emt != NULL ) {
14337             /* New style */
14338             char delim;
14339
14340             if( s_eval != NULL ) {
14341                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14342                     return text;
14343                 }
14344
14345                 if( delim != ']' ) {
14346                     return text;
14347                 }
14348             }
14349
14350             if( s_emt != NULL ) {
14351             }
14352                 return text;
14353         }
14354         else {
14355             /* We expect something like: [+|-]nnn.nn/dd */
14356             int score_lo = 0;
14357
14358             if(*text != '{') return text; // [HGM] braces: must be normal comment
14359
14360             sep = strchr( text, '/' );
14361             if( sep == NULL || sep < (text+4) ) {
14362                 return text;
14363             }
14364
14365             p = text;
14366             if(p[1] == '(') { // comment starts with PV
14367                p = strchr(p, ')'); // locate end of PV
14368                if(p == NULL || sep < p+5) return text;
14369                // at this point we have something like "{(.*) +0.23/6 ..."
14370                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14371                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14372                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14373             }
14374             time = -1; sec = -1; deci = -1;
14375             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14376                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14377                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14378                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14379                 return text;
14380             }
14381
14382             if( score_lo < 0 || score_lo >= 100 ) {
14383                 return text;
14384             }
14385
14386             if(sec >= 0) time = 600*time + 10*sec; else
14387             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14388
14389             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14390
14391             /* [HGM] PV time: now locate end of PV info */
14392             while( *++sep >= '0' && *sep <= '9'); // strip depth
14393             if(time >= 0)
14394             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14395             if(sec >= 0)
14396             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14397             if(deci >= 0)
14398             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14399             while(*sep == ' ') sep++;
14400         }
14401
14402         if( depth <= 0 ) {
14403             return text;
14404         }
14405
14406         if( time < 0 ) {
14407             time = -1;
14408         }
14409
14410         pvInfoList[index-1].depth = depth;
14411         pvInfoList[index-1].score = score;
14412         pvInfoList[index-1].time  = 10*time; // centi-sec
14413         if(*sep == '}') *sep = 0; else *--sep = '{';
14414         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14415     }
14416     return sep;
14417 }
14418
14419 void
14420 SendToProgram(message, cps)
14421      char *message;
14422      ChessProgramState *cps;
14423 {
14424     int count, outCount, error;
14425     char buf[MSG_SIZ];
14426
14427     if (cps->pr == NULL) return;
14428     Attention(cps);
14429
14430     if (appData.debugMode) {
14431         TimeMark now;
14432         GetTimeMark(&now);
14433         fprintf(debugFP, "%ld >%-6s: %s",
14434                 SubtractTimeMarks(&now, &programStartTime),
14435                 cps->which, message);
14436     }
14437
14438     count = strlen(message);
14439     outCount = OutputToProcess(cps->pr, message, count, &error);
14440     if (outCount < count && !exiting
14441                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14442       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14443       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14444         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14445             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14446                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14447                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14448                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14449             } else {
14450                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14451                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14452                 gameInfo.result = res;
14453             }
14454             gameInfo.resultDetails = StrSave(buf);
14455         }
14456         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14457         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14458     }
14459 }
14460
14461 void
14462 ReceiveFromProgram(isr, closure, message, count, error)
14463      InputSourceRef isr;
14464      VOIDSTAR closure;
14465      char *message;
14466      int count;
14467      int error;
14468 {
14469     char *end_str;
14470     char buf[MSG_SIZ];
14471     ChessProgramState *cps = (ChessProgramState *)closure;
14472
14473     if (isr != cps->isr) return; /* Killed intentionally */
14474     if (count <= 0) {
14475         if (count == 0) {
14476             RemoveInputSource(cps->isr);
14477             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14478             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14479                     _(cps->which), cps->program);
14480         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14481                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14482                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14483                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14484                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14485                 } else {
14486                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14487                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14488                     gameInfo.result = res;
14489                 }
14490                 gameInfo.resultDetails = StrSave(buf);
14491             }
14492             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14493             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14494         } else {
14495             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14496                     _(cps->which), cps->program);
14497             RemoveInputSource(cps->isr);
14498
14499             /* [AS] Program is misbehaving badly... kill it */
14500             if( count == -2 ) {
14501                 DestroyChildProcess( cps->pr, 9 );
14502                 cps->pr = NoProc;
14503             }
14504
14505             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14506         }
14507         return;
14508     }
14509
14510     if ((end_str = strchr(message, '\r')) != NULL)
14511       *end_str = NULLCHAR;
14512     if ((end_str = strchr(message, '\n')) != NULL)
14513       *end_str = NULLCHAR;
14514
14515     if (appData.debugMode) {
14516         TimeMark now; int print = 1;
14517         char *quote = ""; char c; int i;
14518
14519         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14520                 char start = message[0];
14521                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14522                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14523                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14524                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14525                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14526                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14527                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14528                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14529                    sscanf(message, "hint: %c", &c)!=1 && 
14530                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14531                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14532                     print = (appData.engineComments >= 2);
14533                 }
14534                 message[0] = start; // restore original message
14535         }
14536         if(print) {
14537                 GetTimeMark(&now);
14538                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14539                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14540                         quote,
14541                         message);
14542         }
14543     }
14544
14545     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14546     if (appData.icsEngineAnalyze) {
14547         if (strstr(message, "whisper") != NULL ||
14548              strstr(message, "kibitz") != NULL ||
14549             strstr(message, "tellics") != NULL) return;
14550     }
14551
14552     HandleMachineMove(message, cps);
14553 }
14554
14555
14556 void
14557 SendTimeControl(cps, mps, tc, inc, sd, st)
14558      ChessProgramState *cps;
14559      int mps, inc, sd, st;
14560      long tc;
14561 {
14562     char buf[MSG_SIZ];
14563     int seconds;
14564
14565     if( timeControl_2 > 0 ) {
14566         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14567             tc = timeControl_2;
14568         }
14569     }
14570     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14571     inc /= cps->timeOdds;
14572     st  /= cps->timeOdds;
14573
14574     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14575
14576     if (st > 0) {
14577       /* Set exact time per move, normally using st command */
14578       if (cps->stKludge) {
14579         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14580         seconds = st % 60;
14581         if (seconds == 0) {
14582           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14583         } else {
14584           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14585         }
14586       } else {
14587         snprintf(buf, MSG_SIZ, "st %d\n", st);
14588       }
14589     } else {
14590       /* Set conventional or incremental time control, using level command */
14591       if (seconds == 0) {
14592         /* Note old gnuchess bug -- minutes:seconds used to not work.
14593            Fixed in later versions, but still avoid :seconds
14594            when seconds is 0. */
14595         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14596       } else {
14597         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14598                  seconds, inc/1000.);
14599       }
14600     }
14601     SendToProgram(buf, cps);
14602
14603     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14604     /* Orthogonally, limit search to given depth */
14605     if (sd > 0) {
14606       if (cps->sdKludge) {
14607         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14608       } else {
14609         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14610       }
14611       SendToProgram(buf, cps);
14612     }
14613
14614     if(cps->nps >= 0) { /* [HGM] nps */
14615         if(cps->supportsNPS == FALSE)
14616           cps->nps = -1; // don't use if engine explicitly says not supported!
14617         else {
14618           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14619           SendToProgram(buf, cps);
14620         }
14621     }
14622 }
14623
14624 ChessProgramState *WhitePlayer()
14625 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14626 {
14627     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14628        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14629         return &second;
14630     return &first;
14631 }
14632
14633 void
14634 SendTimeRemaining(cps, machineWhite)
14635      ChessProgramState *cps;
14636      int /*boolean*/ machineWhite;
14637 {
14638     char message[MSG_SIZ];
14639     long time, otime;
14640
14641     /* Note: this routine must be called when the clocks are stopped
14642        or when they have *just* been set or switched; otherwise
14643        it will be off by the time since the current tick started.
14644     */
14645     if (machineWhite) {
14646         time = whiteTimeRemaining / 10;
14647         otime = blackTimeRemaining / 10;
14648     } else {
14649         time = blackTimeRemaining / 10;
14650         otime = whiteTimeRemaining / 10;
14651     }
14652     /* [HGM] translate opponent's time by time-odds factor */
14653     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14654     if (appData.debugMode) {
14655         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14656     }
14657
14658     if (time <= 0) time = 1;
14659     if (otime <= 0) otime = 1;
14660
14661     snprintf(message, MSG_SIZ, "time %ld\n", time);
14662     SendToProgram(message, cps);
14663
14664     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14665     SendToProgram(message, cps);
14666 }
14667
14668 int
14669 BoolFeature(p, name, loc, cps)
14670      char **p;
14671      char *name;
14672      int *loc;
14673      ChessProgramState *cps;
14674 {
14675   char buf[MSG_SIZ];
14676   int len = strlen(name);
14677   int val;
14678
14679   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14680     (*p) += len + 1;
14681     sscanf(*p, "%d", &val);
14682     *loc = (val != 0);
14683     while (**p && **p != ' ')
14684       (*p)++;
14685     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14686     SendToProgram(buf, cps);
14687     return TRUE;
14688   }
14689   return FALSE;
14690 }
14691
14692 int
14693 IntFeature(p, name, loc, cps)
14694      char **p;
14695      char *name;
14696      int *loc;
14697      ChessProgramState *cps;
14698 {
14699   char buf[MSG_SIZ];
14700   int len = strlen(name);
14701   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14702     (*p) += len + 1;
14703     sscanf(*p, "%d", loc);
14704     while (**p && **p != ' ') (*p)++;
14705     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14706     SendToProgram(buf, cps);
14707     return TRUE;
14708   }
14709   return FALSE;
14710 }
14711
14712 int
14713 StringFeature(p, name, loc, cps)
14714      char **p;
14715      char *name;
14716      char loc[];
14717      ChessProgramState *cps;
14718 {
14719   char buf[MSG_SIZ];
14720   int len = strlen(name);
14721   if (strncmp((*p), name, len) == 0
14722       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14723     (*p) += len + 2;
14724     sscanf(*p, "%[^\"]", loc);
14725     while (**p && **p != '\"') (*p)++;
14726     if (**p == '\"') (*p)++;
14727     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14728     SendToProgram(buf, cps);
14729     return TRUE;
14730   }
14731   return FALSE;
14732 }
14733
14734 int
14735 ParseOption(Option *opt, ChessProgramState *cps)
14736 // [HGM] options: process the string that defines an engine option, and determine
14737 // name, type, default value, and allowed value range
14738 {
14739         char *p, *q, buf[MSG_SIZ];
14740         int n, min = (-1)<<31, max = 1<<31, def;
14741
14742         if(p = strstr(opt->name, " -spin ")) {
14743             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14744             if(max < min) max = min; // enforce consistency
14745             if(def < min) def = min;
14746             if(def > max) def = max;
14747             opt->value = def;
14748             opt->min = min;
14749             opt->max = max;
14750             opt->type = Spin;
14751         } else if((p = strstr(opt->name, " -slider "))) {
14752             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14753             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14754             if(max < min) max = min; // enforce consistency
14755             if(def < min) def = min;
14756             if(def > max) def = max;
14757             opt->value = def;
14758             opt->min = min;
14759             opt->max = max;
14760             opt->type = Spin; // Slider;
14761         } else if((p = strstr(opt->name, " -string "))) {
14762             opt->textValue = p+9;
14763             opt->type = TextBox;
14764         } else if((p = strstr(opt->name, " -file "))) {
14765             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14766             opt->textValue = p+7;
14767             opt->type = FileName; // FileName;
14768         } else if((p = strstr(opt->name, " -path "))) {
14769             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14770             opt->textValue = p+7;
14771             opt->type = PathName; // PathName;
14772         } else if(p = strstr(opt->name, " -check ")) {
14773             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14774             opt->value = (def != 0);
14775             opt->type = CheckBox;
14776         } else if(p = strstr(opt->name, " -combo ")) {
14777             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14778             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14779             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14780             opt->value = n = 0;
14781             while(q = StrStr(q, " /// ")) {
14782                 n++; *q = 0;    // count choices, and null-terminate each of them
14783                 q += 5;
14784                 if(*q == '*') { // remember default, which is marked with * prefix
14785                     q++;
14786                     opt->value = n;
14787                 }
14788                 cps->comboList[cps->comboCnt++] = q;
14789             }
14790             cps->comboList[cps->comboCnt++] = NULL;
14791             opt->max = n + 1;
14792             opt->type = ComboBox;
14793         } else if(p = strstr(opt->name, " -button")) {
14794             opt->type = Button;
14795         } else if(p = strstr(opt->name, " -save")) {
14796             opt->type = SaveButton;
14797         } else return FALSE;
14798         *p = 0; // terminate option name
14799         // now look if the command-line options define a setting for this engine option.
14800         if(cps->optionSettings && cps->optionSettings[0])
14801             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14802         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14803           snprintf(buf, MSG_SIZ, "option %s", p);
14804                 if(p = strstr(buf, ",")) *p = 0;
14805                 if(q = strchr(buf, '=')) switch(opt->type) {
14806                     case ComboBox:
14807                         for(n=0; n<opt->max; n++)
14808                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14809                         break;
14810                     case TextBox:
14811                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14812                         break;
14813                     case Spin:
14814                     case CheckBox:
14815                         opt->value = atoi(q+1);
14816                     default:
14817                         break;
14818                 }
14819                 strcat(buf, "\n");
14820                 SendToProgram(buf, cps);
14821         }
14822         return TRUE;
14823 }
14824
14825 void
14826 FeatureDone(cps, val)
14827      ChessProgramState* cps;
14828      int val;
14829 {
14830   DelayedEventCallback cb = GetDelayedEvent();
14831   if ((cb == InitBackEnd3 && cps == &first) ||
14832       (cb == SettingsMenuIfReady && cps == &second) ||
14833       (cb == LoadEngine) ||
14834       (cb == TwoMachinesEventIfReady)) {
14835     CancelDelayedEvent();
14836     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14837   }
14838   cps->initDone = val;
14839 }
14840
14841 /* Parse feature command from engine */
14842 void
14843 ParseFeatures(args, cps)
14844      char* args;
14845      ChessProgramState *cps;
14846 {
14847   char *p = args;
14848   char *q;
14849   int val;
14850   char buf[MSG_SIZ];
14851
14852   for (;;) {
14853     while (*p == ' ') p++;
14854     if (*p == NULLCHAR) return;
14855
14856     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14857     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14858     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14859     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14860     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14861     if (BoolFeature(&p, "reuse", &val, cps)) {
14862       /* Engine can disable reuse, but can't enable it if user said no */
14863       if (!val) cps->reuse = FALSE;
14864       continue;
14865     }
14866     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14867     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14868       if (gameMode == TwoMachinesPlay) {
14869         DisplayTwoMachinesTitle();
14870       } else {
14871         DisplayTitle("");
14872       }
14873       continue;
14874     }
14875     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14876     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14877     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14878     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14879     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14880     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14881     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14882     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14883     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14884     if (IntFeature(&p, "done", &val, cps)) {
14885       FeatureDone(cps, val);
14886       continue;
14887     }
14888     /* Added by Tord: */
14889     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14890     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14891     /* End of additions by Tord */
14892
14893     /* [HGM] added features: */
14894     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14895     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14896     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14897     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14898     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14899     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14900     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14901         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14902           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14903             SendToProgram(buf, cps);
14904             continue;
14905         }
14906         if(cps->nrOptions >= MAX_OPTIONS) {
14907             cps->nrOptions--;
14908             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14909             DisplayError(buf, 0);
14910         }
14911         continue;
14912     }
14913     /* End of additions by HGM */
14914
14915     /* unknown feature: complain and skip */
14916     q = p;
14917     while (*q && *q != '=') q++;
14918     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14919     SendToProgram(buf, cps);
14920     p = q;
14921     if (*p == '=') {
14922       p++;
14923       if (*p == '\"') {
14924         p++;
14925         while (*p && *p != '\"') p++;
14926         if (*p == '\"') p++;
14927       } else {
14928         while (*p && *p != ' ') p++;
14929       }
14930     }
14931   }
14932
14933 }
14934
14935 void
14936 PeriodicUpdatesEvent(newState)
14937      int newState;
14938 {
14939     if (newState == appData.periodicUpdates)
14940       return;
14941
14942     appData.periodicUpdates=newState;
14943
14944     /* Display type changes, so update it now */
14945 //    DisplayAnalysis();
14946
14947     /* Get the ball rolling again... */
14948     if (newState) {
14949         AnalysisPeriodicEvent(1);
14950         StartAnalysisClock();
14951     }
14952 }
14953
14954 void
14955 PonderNextMoveEvent(newState)
14956      int newState;
14957 {
14958     if (newState == appData.ponderNextMove) return;
14959     if (gameMode == EditPosition) EditPositionDone(TRUE);
14960     if (newState) {
14961         SendToProgram("hard\n", &first);
14962         if (gameMode == TwoMachinesPlay) {
14963             SendToProgram("hard\n", &second);
14964         }
14965     } else {
14966         SendToProgram("easy\n", &first);
14967         thinkOutput[0] = NULLCHAR;
14968         if (gameMode == TwoMachinesPlay) {
14969             SendToProgram("easy\n", &second);
14970         }
14971     }
14972     appData.ponderNextMove = newState;
14973 }
14974
14975 void
14976 NewSettingEvent(option, feature, command, value)
14977      char *command;
14978      int option, value, *feature;
14979 {
14980     char buf[MSG_SIZ];
14981
14982     if (gameMode == EditPosition) EditPositionDone(TRUE);
14983     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14984     if(feature == NULL || *feature) SendToProgram(buf, &first);
14985     if (gameMode == TwoMachinesPlay) {
14986         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14987     }
14988 }
14989
14990 void
14991 ShowThinkingEvent()
14992 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14993 {
14994     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14995     int newState = appData.showThinking
14996         // [HGM] thinking: other features now need thinking output as well
14997         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14998
14999     if (oldState == newState) return;
15000     oldState = newState;
15001     if (gameMode == EditPosition) EditPositionDone(TRUE);
15002     if (oldState) {
15003         SendToProgram("post\n", &first);
15004         if (gameMode == TwoMachinesPlay) {
15005             SendToProgram("post\n", &second);
15006         }
15007     } else {
15008         SendToProgram("nopost\n", &first);
15009         thinkOutput[0] = NULLCHAR;
15010         if (gameMode == TwoMachinesPlay) {
15011             SendToProgram("nopost\n", &second);
15012         }
15013     }
15014 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15015 }
15016
15017 void
15018 AskQuestionEvent(title, question, replyPrefix, which)
15019      char *title; char *question; char *replyPrefix; char *which;
15020 {
15021   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15022   if (pr == NoProc) return;
15023   AskQuestion(title, question, replyPrefix, pr);
15024 }
15025
15026 void
15027 TypeInEvent(char firstChar)
15028 {
15029     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15030         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15031         gameMode == AnalyzeMode || gameMode == EditGame || \r
15032         gameMode == EditPosition || gameMode == IcsExamining ||\r
15033         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15034         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15035                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15036                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15037         gameMode == Training) PopUpMoveDialog(firstChar);
15038 }
15039
15040 void
15041 TypeInDoneEvent(char *move)
15042 {
15043         Board board;
15044         int n, fromX, fromY, toX, toY;
15045         char promoChar;
15046         ChessMove moveType;\r
15047
15048         // [HGM] FENedit\r
15049         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15050                 EditPositionPasteFEN(move);\r
15051                 return;\r
15052         }\r
15053         // [HGM] movenum: allow move number to be typed in any mode\r
15054         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15055           ToNrEvent(2*n-1);\r
15056           return;\r
15057         }\r
15058
15059       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15060         gameMode != Training) {\r
15061         DisplayMoveError(_("Displayed move is not current"));\r
15062       } else {\r
15063         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15064           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15065         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15066         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15067           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15068           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15069         } else {\r
15070           DisplayMoveError(_("Could not parse move"));\r
15071         }
15072       }\r
15073 }\r
15074
15075 void
15076 DisplayMove(moveNumber)
15077      int moveNumber;
15078 {
15079     char message[MSG_SIZ];
15080     char res[MSG_SIZ];
15081     char cpThinkOutput[MSG_SIZ];
15082
15083     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15084
15085     if (moveNumber == forwardMostMove - 1 ||
15086         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15087
15088         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15089
15090         if (strchr(cpThinkOutput, '\n')) {
15091             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15092         }
15093     } else {
15094         *cpThinkOutput = NULLCHAR;
15095     }
15096
15097     /* [AS] Hide thinking from human user */
15098     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15099         *cpThinkOutput = NULLCHAR;
15100         if( thinkOutput[0] != NULLCHAR ) {
15101             int i;
15102
15103             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15104                 cpThinkOutput[i] = '.';
15105             }
15106             cpThinkOutput[i] = NULLCHAR;
15107             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15108         }
15109     }
15110
15111     if (moveNumber == forwardMostMove - 1 &&
15112         gameInfo.resultDetails != NULL) {
15113         if (gameInfo.resultDetails[0] == NULLCHAR) {
15114           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15115         } else {
15116           snprintf(res, MSG_SIZ, " {%s} %s",
15117                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15118         }
15119     } else {
15120         res[0] = NULLCHAR;
15121     }
15122
15123     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15124         DisplayMessage(res, cpThinkOutput);
15125     } else {
15126       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15127                 WhiteOnMove(moveNumber) ? " " : ".. ",
15128                 parseList[moveNumber], res);
15129         DisplayMessage(message, cpThinkOutput);
15130     }
15131 }
15132
15133 void
15134 DisplayComment(moveNumber, text)
15135      int moveNumber;
15136      char *text;
15137 {
15138     char title[MSG_SIZ];
15139     char buf[8000]; // comment can be long!
15140     int score, depth;
15141
15142     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15143       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15144     } else {
15145       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15146               WhiteOnMove(moveNumber) ? " " : ".. ",
15147               parseList[moveNumber]);
15148     }
15149     // [HGM] PV info: display PV info together with (or as) comment
15150     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15151       if(text == NULL) text = "";
15152       score = pvInfoList[moveNumber].score;
15153       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15154               depth, (pvInfoList[moveNumber].time+50)/100, text);
15155       text = buf;
15156     }
15157     if (text != NULL && (appData.autoDisplayComment || commentUp))
15158         CommentPopUp(title, text);
15159 }
15160
15161 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15162  * might be busy thinking or pondering.  It can be omitted if your
15163  * gnuchess is configured to stop thinking immediately on any user
15164  * input.  However, that gnuchess feature depends on the FIONREAD
15165  * ioctl, which does not work properly on some flavors of Unix.
15166  */
15167 void
15168 Attention(cps)
15169      ChessProgramState *cps;
15170 {
15171 #if ATTENTION
15172     if (!cps->useSigint) return;
15173     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15174     switch (gameMode) {
15175       case MachinePlaysWhite:
15176       case MachinePlaysBlack:
15177       case TwoMachinesPlay:
15178       case IcsPlayingWhite:
15179       case IcsPlayingBlack:
15180       case AnalyzeMode:
15181       case AnalyzeFile:
15182         /* Skip if we know it isn't thinking */
15183         if (!cps->maybeThinking) return;
15184         if (appData.debugMode)
15185           fprintf(debugFP, "Interrupting %s\n", cps->which);
15186         InterruptChildProcess(cps->pr);
15187         cps->maybeThinking = FALSE;
15188         break;
15189       default:
15190         break;
15191     }
15192 #endif /*ATTENTION*/
15193 }
15194
15195 int
15196 CheckFlags()
15197 {
15198     if (whiteTimeRemaining <= 0) {
15199         if (!whiteFlag) {
15200             whiteFlag = TRUE;
15201             if (appData.icsActive) {
15202                 if (appData.autoCallFlag &&
15203                     gameMode == IcsPlayingBlack && !blackFlag) {
15204                   SendToICS(ics_prefix);
15205                   SendToICS("flag\n");
15206                 }
15207             } else {
15208                 if (blackFlag) {
15209                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15210                 } else {
15211                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15212                     if (appData.autoCallFlag) {
15213                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15214                         return TRUE;
15215                     }
15216                 }
15217             }
15218         }
15219     }
15220     if (blackTimeRemaining <= 0) {
15221         if (!blackFlag) {
15222             blackFlag = TRUE;
15223             if (appData.icsActive) {
15224                 if (appData.autoCallFlag &&
15225                     gameMode == IcsPlayingWhite && !whiteFlag) {
15226                   SendToICS(ics_prefix);
15227                   SendToICS("flag\n");
15228                 }
15229             } else {
15230                 if (whiteFlag) {
15231                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15232                 } else {
15233                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15234                     if (appData.autoCallFlag) {
15235                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15236                         return TRUE;
15237                     }
15238                 }
15239             }
15240         }
15241     }
15242     return FALSE;
15243 }
15244
15245 void
15246 CheckTimeControl()
15247 {
15248     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15249         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15250
15251     /*
15252      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15253      */
15254     if ( !WhiteOnMove(forwardMostMove) ) {
15255         /* White made time control */
15256         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15257         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15258         /* [HGM] time odds: correct new time quota for time odds! */
15259                                             / WhitePlayer()->timeOdds;
15260         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15261     } else {
15262         lastBlack -= blackTimeRemaining;
15263         /* Black made time control */
15264         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15265                                             / WhitePlayer()->other->timeOdds;
15266         lastWhite = whiteTimeRemaining;
15267     }
15268 }
15269
15270 void
15271 DisplayBothClocks()
15272 {
15273     int wom = gameMode == EditPosition ?
15274       !blackPlaysFirst : WhiteOnMove(currentMove);
15275     DisplayWhiteClock(whiteTimeRemaining, wom);
15276     DisplayBlackClock(blackTimeRemaining, !wom);
15277 }
15278
15279
15280 /* Timekeeping seems to be a portability nightmare.  I think everyone
15281    has ftime(), but I'm really not sure, so I'm including some ifdefs
15282    to use other calls if you don't.  Clocks will be less accurate if
15283    you have neither ftime nor gettimeofday.
15284 */
15285
15286 /* VS 2008 requires the #include outside of the function */
15287 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15288 #include <sys/timeb.h>
15289 #endif
15290
15291 /* Get the current time as a TimeMark */
15292 void
15293 GetTimeMark(tm)
15294      TimeMark *tm;
15295 {
15296 #if HAVE_GETTIMEOFDAY
15297
15298     struct timeval timeVal;
15299     struct timezone timeZone;
15300
15301     gettimeofday(&timeVal, &timeZone);
15302     tm->sec = (long) timeVal.tv_sec;
15303     tm->ms = (int) (timeVal.tv_usec / 1000L);
15304
15305 #else /*!HAVE_GETTIMEOFDAY*/
15306 #if HAVE_FTIME
15307
15308 // include <sys/timeb.h> / moved to just above start of function
15309     struct timeb timeB;
15310
15311     ftime(&timeB);
15312     tm->sec = (long) timeB.time;
15313     tm->ms = (int) timeB.millitm;
15314
15315 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15316     tm->sec = (long) time(NULL);
15317     tm->ms = 0;
15318 #endif
15319 #endif
15320 }
15321
15322 /* Return the difference in milliseconds between two
15323    time marks.  We assume the difference will fit in a long!
15324 */
15325 long
15326 SubtractTimeMarks(tm2, tm1)
15327      TimeMark *tm2, *tm1;
15328 {
15329     return 1000L*(tm2->sec - tm1->sec) +
15330            (long) (tm2->ms - tm1->ms);
15331 }
15332
15333
15334 /*
15335  * Code to manage the game clocks.
15336  *
15337  * In tournament play, black starts the clock and then white makes a move.
15338  * We give the human user a slight advantage if he is playing white---the
15339  * clocks don't run until he makes his first move, so it takes zero time.
15340  * Also, we don't account for network lag, so we could get out of sync
15341  * with GNU Chess's clock -- but then, referees are always right.
15342  */
15343
15344 static TimeMark tickStartTM;
15345 static long intendedTickLength;
15346
15347 long
15348 NextTickLength(timeRemaining)
15349      long timeRemaining;
15350 {
15351     long nominalTickLength, nextTickLength;
15352
15353     if (timeRemaining > 0L && timeRemaining <= 10000L)
15354       nominalTickLength = 100L;
15355     else
15356       nominalTickLength = 1000L;
15357     nextTickLength = timeRemaining % nominalTickLength;
15358     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15359
15360     return nextTickLength;
15361 }
15362
15363 /* Adjust clock one minute up or down */
15364 void
15365 AdjustClock(Boolean which, int dir)
15366 {
15367     if(which) blackTimeRemaining += 60000*dir;
15368     else      whiteTimeRemaining += 60000*dir;
15369     DisplayBothClocks();
15370 }
15371
15372 /* Stop clocks and reset to a fresh time control */
15373 void
15374 ResetClocks()
15375 {
15376     (void) StopClockTimer();
15377     if (appData.icsActive) {
15378         whiteTimeRemaining = blackTimeRemaining = 0;
15379     } else if (searchTime) {
15380         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15381         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15382     } else { /* [HGM] correct new time quote for time odds */
15383         whiteTC = blackTC = fullTimeControlString;
15384         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15385         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15386     }
15387     if (whiteFlag || blackFlag) {
15388         DisplayTitle("");
15389         whiteFlag = blackFlag = FALSE;
15390     }
15391     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15392     DisplayBothClocks();
15393 }
15394
15395 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15396
15397 /* Decrement running clock by amount of time that has passed */
15398 void
15399 DecrementClocks()
15400 {
15401     long timeRemaining;
15402     long lastTickLength, fudge;
15403     TimeMark now;
15404
15405     if (!appData.clockMode) return;
15406     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15407
15408     GetTimeMark(&now);
15409
15410     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15411
15412     /* Fudge if we woke up a little too soon */
15413     fudge = intendedTickLength - lastTickLength;
15414     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15415
15416     if (WhiteOnMove(forwardMostMove)) {
15417         if(whiteNPS >= 0) lastTickLength = 0;
15418         timeRemaining = whiteTimeRemaining -= lastTickLength;
15419         if(timeRemaining < 0 && !appData.icsActive) {
15420             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15421             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15422                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15423                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15424             }
15425         }
15426         DisplayWhiteClock(whiteTimeRemaining - fudge,
15427                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15428     } else {
15429         if(blackNPS >= 0) lastTickLength = 0;
15430         timeRemaining = blackTimeRemaining -= lastTickLength;
15431         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15432             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15433             if(suddenDeath) {
15434                 blackStartMove = forwardMostMove;
15435                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15436             }
15437         }
15438         DisplayBlackClock(blackTimeRemaining - fudge,
15439                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15440     }
15441     if (CheckFlags()) return;
15442
15443     tickStartTM = now;
15444     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15445     StartClockTimer(intendedTickLength);
15446
15447     /* if the time remaining has fallen below the alarm threshold, sound the
15448      * alarm. if the alarm has sounded and (due to a takeback or time control
15449      * with increment) the time remaining has increased to a level above the
15450      * threshold, reset the alarm so it can sound again.
15451      */
15452
15453     if (appData.icsActive && appData.icsAlarm) {
15454
15455         /* make sure we are dealing with the user's clock */
15456         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15457                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15458            )) return;
15459
15460         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15461             alarmSounded = FALSE;
15462         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15463             PlayAlarmSound();
15464             alarmSounded = TRUE;
15465         }
15466     }
15467 }
15468
15469
15470 /* A player has just moved, so stop the previously running
15471    clock and (if in clock mode) start the other one.
15472    We redisplay both clocks in case we're in ICS mode, because
15473    ICS gives us an update to both clocks after every move.
15474    Note that this routine is called *after* forwardMostMove
15475    is updated, so the last fractional tick must be subtracted
15476    from the color that is *not* on move now.
15477 */
15478 void
15479 SwitchClocks(int newMoveNr)
15480 {
15481     long lastTickLength;
15482     TimeMark now;
15483     int flagged = FALSE;
15484
15485     GetTimeMark(&now);
15486
15487     if (StopClockTimer() && appData.clockMode) {
15488         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15489         if (!WhiteOnMove(forwardMostMove)) {
15490             if(blackNPS >= 0) lastTickLength = 0;
15491             blackTimeRemaining -= lastTickLength;
15492            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15493 //         if(pvInfoList[forwardMostMove].time == -1)
15494                  pvInfoList[forwardMostMove].time =               // use GUI time
15495                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15496         } else {
15497            if(whiteNPS >= 0) lastTickLength = 0;
15498            whiteTimeRemaining -= lastTickLength;
15499            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15500 //         if(pvInfoList[forwardMostMove].time == -1)
15501                  pvInfoList[forwardMostMove].time =
15502                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15503         }
15504         flagged = CheckFlags();
15505     }
15506     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15507     CheckTimeControl();
15508
15509     if (flagged || !appData.clockMode) return;
15510
15511     switch (gameMode) {
15512       case MachinePlaysBlack:
15513       case MachinePlaysWhite:
15514       case BeginningOfGame:
15515         if (pausing) return;
15516         break;
15517
15518       case EditGame:
15519       case PlayFromGameFile:
15520       case IcsExamining:
15521         return;
15522
15523       default:
15524         break;
15525     }
15526
15527     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15528         if(WhiteOnMove(forwardMostMove))
15529              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15530         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15531     }
15532
15533     tickStartTM = now;
15534     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15535       whiteTimeRemaining : blackTimeRemaining);
15536     StartClockTimer(intendedTickLength);
15537 }
15538
15539
15540 /* Stop both clocks */
15541 void
15542 StopClocks()
15543 {
15544     long lastTickLength;
15545     TimeMark now;
15546
15547     if (!StopClockTimer()) return;
15548     if (!appData.clockMode) return;
15549
15550     GetTimeMark(&now);
15551
15552     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15553     if (WhiteOnMove(forwardMostMove)) {
15554         if(whiteNPS >= 0) lastTickLength = 0;
15555         whiteTimeRemaining -= lastTickLength;
15556         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15557     } else {
15558         if(blackNPS >= 0) lastTickLength = 0;
15559         blackTimeRemaining -= lastTickLength;
15560         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15561     }
15562     CheckFlags();
15563 }
15564
15565 /* Start clock of player on move.  Time may have been reset, so
15566    if clock is already running, stop and restart it. */
15567 void
15568 StartClocks()
15569 {
15570     (void) StopClockTimer(); /* in case it was running already */
15571     DisplayBothClocks();
15572     if (CheckFlags()) return;
15573
15574     if (!appData.clockMode) return;
15575     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15576
15577     GetTimeMark(&tickStartTM);
15578     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15579       whiteTimeRemaining : blackTimeRemaining);
15580
15581    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15582     whiteNPS = blackNPS = -1;
15583     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15584        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15585         whiteNPS = first.nps;
15586     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15587        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15588         blackNPS = first.nps;
15589     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15590         whiteNPS = second.nps;
15591     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15592         blackNPS = second.nps;
15593     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15594
15595     StartClockTimer(intendedTickLength);
15596 }
15597
15598 char *
15599 TimeString(ms)
15600      long ms;
15601 {
15602     long second, minute, hour, day;
15603     char *sign = "";
15604     static char buf[32];
15605
15606     if (ms > 0 && ms <= 9900) {
15607       /* convert milliseconds to tenths, rounding up */
15608       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15609
15610       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15611       return buf;
15612     }
15613
15614     /* convert milliseconds to seconds, rounding up */
15615     /* use floating point to avoid strangeness of integer division
15616        with negative dividends on many machines */
15617     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15618
15619     if (second < 0) {
15620         sign = "-";
15621         second = -second;
15622     }
15623
15624     day = second / (60 * 60 * 24);
15625     second = second % (60 * 60 * 24);
15626     hour = second / (60 * 60);
15627     second = second % (60 * 60);
15628     minute = second / 60;
15629     second = second % 60;
15630
15631     if (day > 0)
15632       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15633               sign, day, hour, minute, second);
15634     else if (hour > 0)
15635       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15636     else
15637       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15638
15639     return buf;
15640 }
15641
15642
15643 /*
15644  * This is necessary because some C libraries aren't ANSI C compliant yet.
15645  */
15646 char *
15647 StrStr(string, match)
15648      char *string, *match;
15649 {
15650     int i, length;
15651
15652     length = strlen(match);
15653
15654     for (i = strlen(string) - length; i >= 0; i--, string++)
15655       if (!strncmp(match, string, length))
15656         return string;
15657
15658     return NULL;
15659 }
15660
15661 char *
15662 StrCaseStr(string, match)
15663      char *string, *match;
15664 {
15665     int i, j, length;
15666
15667     length = strlen(match);
15668
15669     for (i = strlen(string) - length; i >= 0; i--, string++) {
15670         for (j = 0; j < length; j++) {
15671             if (ToLower(match[j]) != ToLower(string[j]))
15672               break;
15673         }
15674         if (j == length) return string;
15675     }
15676
15677     return NULL;
15678 }
15679
15680 #ifndef _amigados
15681 int
15682 StrCaseCmp(s1, s2)
15683      char *s1, *s2;
15684 {
15685     char c1, c2;
15686
15687     for (;;) {
15688         c1 = ToLower(*s1++);
15689         c2 = ToLower(*s2++);
15690         if (c1 > c2) return 1;
15691         if (c1 < c2) return -1;
15692         if (c1 == NULLCHAR) return 0;
15693     }
15694 }
15695
15696
15697 int
15698 ToLower(c)
15699      int c;
15700 {
15701     return isupper(c) ? tolower(c) : c;
15702 }
15703
15704
15705 int
15706 ToUpper(c)
15707      int c;
15708 {
15709     return islower(c) ? toupper(c) : c;
15710 }
15711 #endif /* !_amigados    */
15712
15713 char *
15714 StrSave(s)
15715      char *s;
15716 {
15717   char *ret;
15718
15719   if ((ret = (char *) malloc(strlen(s) + 1)))
15720     {
15721       safeStrCpy(ret, s, strlen(s)+1);
15722     }
15723   return ret;
15724 }
15725
15726 char *
15727 StrSavePtr(s, savePtr)
15728      char *s, **savePtr;
15729 {
15730     if (*savePtr) {
15731         free(*savePtr);
15732     }
15733     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15734       safeStrCpy(*savePtr, s, strlen(s)+1);
15735     }
15736     return(*savePtr);
15737 }
15738
15739 char *
15740 PGNDate()
15741 {
15742     time_t clock;
15743     struct tm *tm;
15744     char buf[MSG_SIZ];
15745
15746     clock = time((time_t *)NULL);
15747     tm = localtime(&clock);
15748     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15749             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15750     return StrSave(buf);
15751 }
15752
15753
15754 char *
15755 PositionToFEN(move, overrideCastling)
15756      int move;
15757      char *overrideCastling;
15758 {
15759     int i, j, fromX, fromY, toX, toY;
15760     int whiteToPlay;
15761     char buf[128];
15762     char *p, *q;
15763     int emptycount;
15764     ChessSquare piece;
15765
15766     whiteToPlay = (gameMode == EditPosition) ?
15767       !blackPlaysFirst : (move % 2 == 0);
15768     p = buf;
15769
15770     /* Piece placement data */
15771     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15772         emptycount = 0;
15773         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15774             if (boards[move][i][j] == EmptySquare) {
15775                 emptycount++;
15776             } else { ChessSquare piece = boards[move][i][j];
15777                 if (emptycount > 0) {
15778                     if(emptycount<10) /* [HGM] can be >= 10 */
15779                         *p++ = '0' + emptycount;
15780                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15781                     emptycount = 0;
15782                 }
15783                 if(PieceToChar(piece) == '+') {
15784                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15785                     *p++ = '+';
15786                     piece = (ChessSquare)(DEMOTED piece);
15787                 }
15788                 *p++ = PieceToChar(piece);
15789                 if(p[-1] == '~') {
15790                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15791                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15792                     *p++ = '~';
15793                 }
15794             }
15795         }
15796         if (emptycount > 0) {
15797             if(emptycount<10) /* [HGM] can be >= 10 */
15798                 *p++ = '0' + emptycount;
15799             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15800             emptycount = 0;
15801         }
15802         *p++ = '/';
15803     }
15804     *(p - 1) = ' ';
15805
15806     /* [HGM] print Crazyhouse or Shogi holdings */
15807     if( gameInfo.holdingsWidth ) {
15808         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15809         q = p;
15810         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15811             piece = boards[move][i][BOARD_WIDTH-1];
15812             if( piece != EmptySquare )
15813               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15814                   *p++ = PieceToChar(piece);
15815         }
15816         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15817             piece = boards[move][BOARD_HEIGHT-i-1][0];
15818             if( piece != EmptySquare )
15819               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15820                   *p++ = PieceToChar(piece);
15821         }
15822
15823         if( q == p ) *p++ = '-';
15824         *p++ = ']';
15825         *p++ = ' ';
15826     }
15827
15828     /* Active color */
15829     *p++ = whiteToPlay ? 'w' : 'b';
15830     *p++ = ' ';
15831
15832   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15833     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15834   } else {
15835   if(nrCastlingRights) {
15836      q = p;
15837      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15838        /* [HGM] write directly from rights */
15839            if(boards[move][CASTLING][2] != NoRights &&
15840               boards[move][CASTLING][0] != NoRights   )
15841                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15842            if(boards[move][CASTLING][2] != NoRights &&
15843               boards[move][CASTLING][1] != NoRights   )
15844                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15845            if(boards[move][CASTLING][5] != NoRights &&
15846               boards[move][CASTLING][3] != NoRights   )
15847                 *p++ = boards[move][CASTLING][3] + AAA;
15848            if(boards[move][CASTLING][5] != NoRights &&
15849               boards[move][CASTLING][4] != NoRights   )
15850                 *p++ = boards[move][CASTLING][4] + AAA;
15851      } else {
15852
15853         /* [HGM] write true castling rights */
15854         if( nrCastlingRights == 6 ) {
15855             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15856                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15857             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15858                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15859             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15860                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15861             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15862                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15863         }
15864      }
15865      if (q == p) *p++ = '-'; /* No castling rights */
15866      *p++ = ' ';
15867   }
15868
15869   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15870      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15871     /* En passant target square */
15872     if (move > backwardMostMove) {
15873         fromX = moveList[move - 1][0] - AAA;
15874         fromY = moveList[move - 1][1] - ONE;
15875         toX = moveList[move - 1][2] - AAA;
15876         toY = moveList[move - 1][3] - ONE;
15877         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15878             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15879             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15880             fromX == toX) {
15881             /* 2-square pawn move just happened */
15882             *p++ = toX + AAA;
15883             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15884         } else {
15885             *p++ = '-';
15886         }
15887     } else if(move == backwardMostMove) {
15888         // [HGM] perhaps we should always do it like this, and forget the above?
15889         if((signed char)boards[move][EP_STATUS] >= 0) {
15890             *p++ = boards[move][EP_STATUS] + AAA;
15891             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15892         } else {
15893             *p++ = '-';
15894         }
15895     } else {
15896         *p++ = '-';
15897     }
15898     *p++ = ' ';
15899   }
15900   }
15901
15902     /* [HGM] find reversible plies */
15903     {   int i = 0, j=move;
15904
15905         if (appData.debugMode) { int k;
15906             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15907             for(k=backwardMostMove; k<=forwardMostMove; k++)
15908                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15909
15910         }
15911
15912         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15913         if( j == backwardMostMove ) i += initialRulePlies;
15914         sprintf(p, "%d ", i);
15915         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15916     }
15917     /* Fullmove number */
15918     sprintf(p, "%d", (move / 2) + 1);
15919
15920     return StrSave(buf);
15921 }
15922
15923 Boolean
15924 ParseFEN(board, blackPlaysFirst, fen)
15925     Board board;
15926      int *blackPlaysFirst;
15927      char *fen;
15928 {
15929     int i, j;
15930     char *p, c;
15931     int emptycount;
15932     ChessSquare piece;
15933
15934     p = fen;
15935
15936     /* [HGM] by default clear Crazyhouse holdings, if present */
15937     if(gameInfo.holdingsWidth) {
15938        for(i=0; i<BOARD_HEIGHT; i++) {
15939            board[i][0]             = EmptySquare; /* black holdings */
15940            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15941            board[i][1]             = (ChessSquare) 0; /* black counts */
15942            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15943        }
15944     }
15945
15946     /* Piece placement data */
15947     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15948         j = 0;
15949         for (;;) {
15950             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15951                 if (*p == '/') p++;
15952                 emptycount = gameInfo.boardWidth - j;
15953                 while (emptycount--)
15954                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15955                 break;
15956 #if(BOARD_FILES >= 10)
15957             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15958                 p++; emptycount=10;
15959                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15960                 while (emptycount--)
15961                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15962 #endif
15963             } else if (isdigit(*p)) {
15964                 emptycount = *p++ - '0';
15965                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15966                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15967                 while (emptycount--)
15968                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15969             } else if (*p == '+' || isalpha(*p)) {
15970                 if (j >= gameInfo.boardWidth) return FALSE;
15971                 if(*p=='+') {
15972                     piece = CharToPiece(*++p);
15973                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15974                     piece = (ChessSquare) (PROMOTED piece ); p++;
15975                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15976                 } else piece = CharToPiece(*p++);
15977
15978                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15979                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15980                     piece = (ChessSquare) (PROMOTED piece);
15981                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15982                     p++;
15983                 }
15984                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15985             } else {
15986                 return FALSE;
15987             }
15988         }
15989     }
15990     while (*p == '/' || *p == ' ') p++;
15991
15992     /* [HGM] look for Crazyhouse holdings here */
15993     while(*p==' ') p++;
15994     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15995         if(*p == '[') p++;
15996         if(*p == '-' ) p++; /* empty holdings */ else {
15997             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15998             /* if we would allow FEN reading to set board size, we would   */
15999             /* have to add holdings and shift the board read so far here   */
16000             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16001                 p++;
16002                 if((int) piece >= (int) BlackPawn ) {
16003                     i = (int)piece - (int)BlackPawn;
16004                     i = PieceToNumber((ChessSquare)i);
16005                     if( i >= gameInfo.holdingsSize ) return FALSE;
16006                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16007                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16008                 } else {
16009                     i = (int)piece - (int)WhitePawn;
16010                     i = PieceToNumber((ChessSquare)i);
16011                     if( i >= gameInfo.holdingsSize ) return FALSE;
16012                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16013                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16014                 }
16015             }
16016         }
16017         if(*p == ']') p++;
16018     }
16019
16020     while(*p == ' ') p++;
16021
16022     /* Active color */
16023     c = *p++;
16024     if(appData.colorNickNames) {
16025       if( c == appData.colorNickNames[0] ) c = 'w'; else
16026       if( c == appData.colorNickNames[1] ) c = 'b';
16027     }
16028     switch (c) {
16029       case 'w':
16030         *blackPlaysFirst = FALSE;
16031         break;
16032       case 'b':
16033         *blackPlaysFirst = TRUE;
16034         break;
16035       default:
16036         return FALSE;
16037     }
16038
16039     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16040     /* return the extra info in global variiables             */
16041
16042     /* set defaults in case FEN is incomplete */
16043     board[EP_STATUS] = EP_UNKNOWN;
16044     for(i=0; i<nrCastlingRights; i++ ) {
16045         board[CASTLING][i] =
16046             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16047     }   /* assume possible unless obviously impossible */
16048     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16049     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16050     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16051                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16052     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16053     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16054     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16055                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16056     FENrulePlies = 0;
16057
16058     while(*p==' ') p++;
16059     if(nrCastlingRights) {
16060       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16061           /* castling indicator present, so default becomes no castlings */
16062           for(i=0; i<nrCastlingRights; i++ ) {
16063                  board[CASTLING][i] = NoRights;
16064           }
16065       }
16066       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16067              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16068              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16069              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16070         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16071
16072         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16073             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16074             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16075         }
16076         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16077             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16078         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16079                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16080         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16081                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16082         switch(c) {
16083           case'K':
16084               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16085               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16086               board[CASTLING][2] = whiteKingFile;
16087               break;
16088           case'Q':
16089               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16090               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16091               board[CASTLING][2] = whiteKingFile;
16092               break;
16093           case'k':
16094               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16095               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16096               board[CASTLING][5] = blackKingFile;
16097               break;
16098           case'q':
16099               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16100               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16101               board[CASTLING][5] = blackKingFile;
16102           case '-':
16103               break;
16104           default: /* FRC castlings */
16105               if(c >= 'a') { /* black rights */
16106                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16107                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16108                   if(i == BOARD_RGHT) break;
16109                   board[CASTLING][5] = i;
16110                   c -= AAA;
16111                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16112                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16113                   if(c > i)
16114                       board[CASTLING][3] = c;
16115                   else
16116                       board[CASTLING][4] = c;
16117               } else { /* white rights */
16118                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16119                     if(board[0][i] == WhiteKing) break;
16120                   if(i == BOARD_RGHT) break;
16121                   board[CASTLING][2] = i;
16122                   c -= AAA - 'a' + 'A';
16123                   if(board[0][c] >= WhiteKing) break;
16124                   if(c > i)
16125                       board[CASTLING][0] = c;
16126                   else
16127                       board[CASTLING][1] = c;
16128               }
16129         }
16130       }
16131       for(i=0; i<nrCastlingRights; i++)
16132         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16133     if (appData.debugMode) {
16134         fprintf(debugFP, "FEN castling rights:");
16135         for(i=0; i<nrCastlingRights; i++)
16136         fprintf(debugFP, " %d", board[CASTLING][i]);
16137         fprintf(debugFP, "\n");
16138     }
16139
16140       while(*p==' ') p++;
16141     }
16142
16143     /* read e.p. field in games that know e.p. capture */
16144     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16145        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16146       if(*p=='-') {
16147         p++; board[EP_STATUS] = EP_NONE;
16148       } else {
16149          char c = *p++ - AAA;
16150
16151          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16152          if(*p >= '0' && *p <='9') p++;
16153          board[EP_STATUS] = c;
16154       }
16155     }
16156
16157
16158     if(sscanf(p, "%d", &i) == 1) {
16159         FENrulePlies = i; /* 50-move ply counter */
16160         /* (The move number is still ignored)    */
16161     }
16162
16163     return TRUE;
16164 }
16165
16166 void
16167 EditPositionPasteFEN(char *fen)
16168 {
16169   if (fen != NULL) {
16170     Board initial_position;
16171
16172     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16173       DisplayError(_("Bad FEN position in clipboard"), 0);
16174       return ;
16175     } else {
16176       int savedBlackPlaysFirst = blackPlaysFirst;
16177       EditPositionEvent();
16178       blackPlaysFirst = savedBlackPlaysFirst;
16179       CopyBoard(boards[0], initial_position);
16180       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16181       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16182       DisplayBothClocks();
16183       DrawPosition(FALSE, boards[currentMove]);
16184     }
16185   }
16186 }
16187
16188 static char cseq[12] = "\\   ";
16189
16190 Boolean set_cont_sequence(char *new_seq)
16191 {
16192     int len;
16193     Boolean ret;
16194
16195     // handle bad attempts to set the sequence
16196         if (!new_seq)
16197                 return 0; // acceptable error - no debug
16198
16199     len = strlen(new_seq);
16200     ret = (len > 0) && (len < sizeof(cseq));
16201     if (ret)
16202       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16203     else if (appData.debugMode)
16204       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16205     return ret;
16206 }
16207
16208 /*
16209     reformat a source message so words don't cross the width boundary.  internal
16210     newlines are not removed.  returns the wrapped size (no null character unless
16211     included in source message).  If dest is NULL, only calculate the size required
16212     for the dest buffer.  lp argument indicats line position upon entry, and it's
16213     passed back upon exit.
16214 */
16215 int wrap(char *dest, char *src, int count, int width, int *lp)
16216 {
16217     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16218
16219     cseq_len = strlen(cseq);
16220     old_line = line = *lp;
16221     ansi = len = clen = 0;
16222
16223     for (i=0; i < count; i++)
16224     {
16225         if (src[i] == '\033')
16226             ansi = 1;
16227
16228         // if we hit the width, back up
16229         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16230         {
16231             // store i & len in case the word is too long
16232             old_i = i, old_len = len;
16233
16234             // find the end of the last word
16235             while (i && src[i] != ' ' && src[i] != '\n')
16236             {
16237                 i--;
16238                 len--;
16239             }
16240
16241             // word too long?  restore i & len before splitting it
16242             if ((old_i-i+clen) >= width)
16243             {
16244                 i = old_i;
16245                 len = old_len;
16246             }
16247
16248             // extra space?
16249             if (i && src[i-1] == ' ')
16250                 len--;
16251
16252             if (src[i] != ' ' && src[i] != '\n')
16253             {
16254                 i--;
16255                 if (len)
16256                     len--;
16257             }
16258
16259             // now append the newline and continuation sequence
16260             if (dest)
16261                 dest[len] = '\n';
16262             len++;
16263             if (dest)
16264                 strncpy(dest+len, cseq, cseq_len);
16265             len += cseq_len;
16266             line = cseq_len;
16267             clen = cseq_len;
16268             continue;
16269         }
16270
16271         if (dest)
16272             dest[len] = src[i];
16273         len++;
16274         if (!ansi)
16275             line++;
16276         if (src[i] == '\n')
16277             line = 0;
16278         if (src[i] == 'm')
16279             ansi = 0;
16280     }
16281     if (dest && appData.debugMode)
16282     {
16283         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16284             count, width, line, len, *lp);
16285         show_bytes(debugFP, src, count);
16286         fprintf(debugFP, "\ndest: ");
16287         show_bytes(debugFP, dest, len);
16288         fprintf(debugFP, "\n");
16289     }
16290     *lp = dest ? line : old_line;
16291
16292     return len;
16293 }
16294
16295 // [HGM] vari: routines for shelving variations
16296
16297 void
16298 PushInner(int firstMove, int lastMove)
16299 {
16300         int i, j, nrMoves = lastMove - firstMove;
16301
16302         // push current tail of game on stack
16303         savedResult[storedGames] = gameInfo.result;
16304         savedDetails[storedGames] = gameInfo.resultDetails;
16305         gameInfo.resultDetails = NULL;
16306         savedFirst[storedGames] = firstMove;
16307         savedLast [storedGames] = lastMove;
16308         savedFramePtr[storedGames] = framePtr;
16309         framePtr -= nrMoves; // reserve space for the boards
16310         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16311             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16312             for(j=0; j<MOVE_LEN; j++)
16313                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16314             for(j=0; j<2*MOVE_LEN; j++)
16315                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16316             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16317             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16318             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16319             pvInfoList[firstMove+i-1].depth = 0;
16320             commentList[framePtr+i] = commentList[firstMove+i];
16321             commentList[firstMove+i] = NULL;
16322         }
16323
16324         storedGames++;
16325         forwardMostMove = firstMove; // truncate game so we can start variation
16326 }
16327
16328 void
16329 PushTail(int firstMove, int lastMove)
16330 {
16331         if(appData.icsActive) { // only in local mode
16332                 forwardMostMove = currentMove; // mimic old ICS behavior
16333                 return;
16334         }
16335         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16336
16337         PushInner(firstMove, lastMove);
16338         if(storedGames == 1) GreyRevert(FALSE);
16339 }
16340
16341 void
16342 PopInner(Boolean annotate)
16343 {
16344         int i, j, nrMoves;
16345         char buf[8000], moveBuf[20];
16346
16347         storedGames--;
16348         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16349         nrMoves = savedLast[storedGames] - currentMove;
16350         if(annotate) {
16351                 int cnt = 10;
16352                 if(!WhiteOnMove(currentMove))
16353                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16354                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16355                 for(i=currentMove; i<forwardMostMove; i++) {
16356                         if(WhiteOnMove(i))
16357                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16358                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16359                         strcat(buf, moveBuf);
16360                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16361                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16362                 }
16363                 strcat(buf, ")");
16364         }
16365         for(i=1; i<=nrMoves; i++) { // copy last variation back
16366             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16367             for(j=0; j<MOVE_LEN; j++)
16368                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16369             for(j=0; j<2*MOVE_LEN; j++)
16370                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16371             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16372             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16373             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16374             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16375             commentList[currentMove+i] = commentList[framePtr+i];
16376             commentList[framePtr+i] = NULL;
16377         }
16378         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16379         framePtr = savedFramePtr[storedGames];
16380         gameInfo.result = savedResult[storedGames];
16381         if(gameInfo.resultDetails != NULL) {
16382             free(gameInfo.resultDetails);
16383       }
16384         gameInfo.resultDetails = savedDetails[storedGames];
16385         forwardMostMove = currentMove + nrMoves;
16386 }
16387
16388 Boolean
16389 PopTail(Boolean annotate)
16390 {
16391         if(appData.icsActive) return FALSE; // only in local mode
16392         if(!storedGames) return FALSE; // sanity
16393         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16394
16395         PopInner(annotate);
16396
16397         if(storedGames == 0) GreyRevert(TRUE);
16398         return TRUE;
16399 }
16400
16401 void
16402 CleanupTail()
16403 {       // remove all shelved variations
16404         int i;
16405         for(i=0; i<storedGames; i++) {
16406             if(savedDetails[i])
16407                 free(savedDetails[i]);
16408             savedDetails[i] = NULL;
16409         }
16410         for(i=framePtr; i<MAX_MOVES; i++) {
16411                 if(commentList[i]) free(commentList[i]);
16412                 commentList[i] = NULL;
16413         }
16414         framePtr = MAX_MOVES-1;
16415         storedGames = 0;
16416 }
16417
16418 void
16419 LoadVariation(int index, char *text)
16420 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16421         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16422         int level = 0, move;
16423
16424         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16425         // first find outermost bracketing variation
16426         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16427             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16428                 if(*p == '{') wait = '}'; else
16429                 if(*p == '[') wait = ']'; else
16430                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16431                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16432             }
16433             if(*p == wait) wait = NULLCHAR; // closing ]} found
16434             p++;
16435         }
16436         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16437         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16438         end[1] = NULLCHAR; // clip off comment beyond variation
16439         ToNrEvent(currentMove-1);
16440         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16441         // kludge: use ParsePV() to append variation to game
16442         move = currentMove;
16443         ParsePV(start, TRUE, TRUE);
16444         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16445         ClearPremoveHighlights();
16446         CommentPopDown();
16447         ToNrEvent(currentMove+1);
16448 }
16449