Fix interrupting tournament
[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             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1554         }
1555         MatchEvent(TRUE);
1556     } else if (*appData.cmailGameName != NULLCHAR) {
1557         /* Set up cmail mode */
1558         ReloadCmailMsgEvent(TRUE);
1559     } else {
1560         /* Set up other modes */
1561         if (initialMode == AnalyzeFile) {
1562           if (*appData.loadGameFile == NULLCHAR) {
1563             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1564             return;
1565           }
1566         }
1567         if (*appData.loadGameFile != NULLCHAR) {
1568             (void) LoadGameFromFile(appData.loadGameFile,
1569                                     appData.loadGameIndex,
1570                                     appData.loadGameFile, TRUE);
1571         } else if (*appData.loadPositionFile != NULLCHAR) {
1572             (void) LoadPositionFromFile(appData.loadPositionFile,
1573                                         appData.loadPositionIndex,
1574                                         appData.loadPositionFile);
1575             /* [HGM] try to make self-starting even after FEN load */
1576             /* to allow automatic setup of fairy variants with wtm */
1577             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1578                 gameMode = BeginningOfGame;
1579                 setboardSpoiledMachineBlack = 1;
1580             }
1581             /* [HGM] loadPos: make that every new game uses the setup */
1582             /* from file as long as we do not switch variant          */
1583             if(!blackPlaysFirst) {
1584                 startedFromPositionFile = TRUE;
1585                 CopyBoard(filePosition, boards[0]);
1586             }
1587         }
1588         if (initialMode == AnalyzeMode) {
1589           if (appData.noChessProgram) {
1590             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1591             return;
1592           }
1593           if (appData.icsActive) {
1594             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1595             return;
1596           }
1597           AnalyzeModeEvent();
1598         } else if (initialMode == AnalyzeFile) {
1599           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1600           ShowThinkingEvent();
1601           AnalyzeFileEvent();
1602           AnalysisPeriodicEvent(1);
1603         } else if (initialMode == MachinePlaysWhite) {
1604           if (appData.noChessProgram) {
1605             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1606                               0, 2);
1607             return;
1608           }
1609           if (appData.icsActive) {
1610             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1611                               0, 2);
1612             return;
1613           }
1614           MachineWhiteEvent();
1615         } else if (initialMode == MachinePlaysBlack) {
1616           if (appData.noChessProgram) {
1617             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1618                               0, 2);
1619             return;
1620           }
1621           if (appData.icsActive) {
1622             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1623                               0, 2);
1624             return;
1625           }
1626           MachineBlackEvent();
1627         } else if (initialMode == TwoMachinesPlay) {
1628           if (appData.noChessProgram) {
1629             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1630                               0, 2);
1631             return;
1632           }
1633           if (appData.icsActive) {
1634             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1635                               0, 2);
1636             return;
1637           }
1638           TwoMachinesEvent();
1639         } else if (initialMode == EditGame) {
1640           EditGameEvent();
1641         } else if (initialMode == EditPosition) {
1642           EditPositionEvent();
1643         } else if (initialMode == Training) {
1644           if (*appData.loadGameFile == NULLCHAR) {
1645             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1646             return;
1647           }
1648           TrainingEvent();
1649         }
1650     }
1651 }
1652
1653 /*
1654  * Establish will establish a contact to a remote host.port.
1655  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1656  *  used to talk to the host.
1657  * Returns 0 if okay, error code if not.
1658  */
1659 int
1660 establish()
1661 {
1662     char buf[MSG_SIZ];
1663
1664     if (*appData.icsCommPort != NULLCHAR) {
1665         /* Talk to the host through a serial comm port */
1666         return OpenCommPort(appData.icsCommPort, &icsPR);
1667
1668     } else if (*appData.gateway != NULLCHAR) {
1669         if (*appData.remoteShell == NULLCHAR) {
1670             /* Use the rcmd protocol to run telnet program on a gateway host */
1671             snprintf(buf, sizeof(buf), "%s %s %s",
1672                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1673             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1674
1675         } else {
1676             /* Use the rsh program to run telnet program on a gateway host */
1677             if (*appData.remoteUser == NULLCHAR) {
1678                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1679                         appData.gateway, appData.telnetProgram,
1680                         appData.icsHost, appData.icsPort);
1681             } else {
1682                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1683                         appData.remoteShell, appData.gateway,
1684                         appData.remoteUser, appData.telnetProgram,
1685                         appData.icsHost, appData.icsPort);
1686             }
1687             return StartChildProcess(buf, "", &icsPR);
1688
1689         }
1690     } else if (appData.useTelnet) {
1691         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1692
1693     } else {
1694         /* TCP socket interface differs somewhat between
1695            Unix and NT; handle details in the front end.
1696            */
1697         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1698     }
1699 }
1700
1701 void EscapeExpand(char *p, char *q)
1702 {       // [HGM] initstring: routine to shape up string arguments
1703         while(*p++ = *q++) if(p[-1] == '\\')
1704             switch(*q++) {
1705                 case 'n': p[-1] = '\n'; break;
1706                 case 'r': p[-1] = '\r'; break;
1707                 case 't': p[-1] = '\t'; break;
1708                 case '\\': p[-1] = '\\'; break;
1709                 case 0: *p = 0; return;
1710                 default: p[-1] = q[-1]; break;
1711             }
1712 }
1713
1714 void
1715 show_bytes(fp, buf, count)
1716      FILE *fp;
1717      char *buf;
1718      int count;
1719 {
1720     while (count--) {
1721         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1722             fprintf(fp, "\\%03o", *buf & 0xff);
1723         } else {
1724             putc(*buf, fp);
1725         }
1726         buf++;
1727     }
1728     fflush(fp);
1729 }
1730
1731 /* Returns an errno value */
1732 int
1733 OutputMaybeTelnet(pr, message, count, outError)
1734      ProcRef pr;
1735      char *message;
1736      int count;
1737      int *outError;
1738 {
1739     char buf[8192], *p, *q, *buflim;
1740     int left, newcount, outcount;
1741
1742     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1743         *appData.gateway != NULLCHAR) {
1744         if (appData.debugMode) {
1745             fprintf(debugFP, ">ICS: ");
1746             show_bytes(debugFP, message, count);
1747             fprintf(debugFP, "\n");
1748         }
1749         return OutputToProcess(pr, message, count, outError);
1750     }
1751
1752     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1753     p = message;
1754     q = buf;
1755     left = count;
1756     newcount = 0;
1757     while (left) {
1758         if (q >= buflim) {
1759             if (appData.debugMode) {
1760                 fprintf(debugFP, ">ICS: ");
1761                 show_bytes(debugFP, buf, newcount);
1762                 fprintf(debugFP, "\n");
1763             }
1764             outcount = OutputToProcess(pr, buf, newcount, outError);
1765             if (outcount < newcount) return -1; /* to be sure */
1766             q = buf;
1767             newcount = 0;
1768         }
1769         if (*p == '\n') {
1770             *q++ = '\r';
1771             newcount++;
1772         } else if (((unsigned char) *p) == TN_IAC) {
1773             *q++ = (char) TN_IAC;
1774             newcount ++;
1775         }
1776         *q++ = *p++;
1777         newcount++;
1778         left--;
1779     }
1780     if (appData.debugMode) {
1781         fprintf(debugFP, ">ICS: ");
1782         show_bytes(debugFP, buf, newcount);
1783         fprintf(debugFP, "\n");
1784     }
1785     outcount = OutputToProcess(pr, buf, newcount, outError);
1786     if (outcount < newcount) return -1; /* to be sure */
1787     return count;
1788 }
1789
1790 void
1791 read_from_player(isr, closure, message, count, error)
1792      InputSourceRef isr;
1793      VOIDSTAR closure;
1794      char *message;
1795      int count;
1796      int error;
1797 {
1798     int outError, outCount;
1799     static int gotEof = 0;
1800
1801     /* Pass data read from player on to ICS */
1802     if (count > 0) {
1803         gotEof = 0;
1804         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1805         if (outCount < count) {
1806             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1807         }
1808     } else if (count < 0) {
1809         RemoveInputSource(isr);
1810         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1811     } else if (gotEof++ > 0) {
1812         RemoveInputSource(isr);
1813         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1814     }
1815 }
1816
1817 void
1818 KeepAlive()
1819 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1820     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1821     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1822     SendToICS("date\n");
1823     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1824 }
1825
1826 /* added routine for printf style output to ics */
1827 void ics_printf(char *format, ...)
1828 {
1829     char buffer[MSG_SIZ];
1830     va_list args;
1831
1832     va_start(args, format);
1833     vsnprintf(buffer, sizeof(buffer), format, args);
1834     buffer[sizeof(buffer)-1] = '\0';
1835     SendToICS(buffer);
1836     va_end(args);
1837 }
1838
1839 void
1840 SendToICS(s)
1841      char *s;
1842 {
1843     int count, outCount, outError;
1844
1845     if (icsPR == NULL) return;
1846
1847     count = strlen(s);
1848     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1849     if (outCount < count) {
1850         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1851     }
1852 }
1853
1854 /* This is used for sending logon scripts to the ICS. Sending
1855    without a delay causes problems when using timestamp on ICC
1856    (at least on my machine). */
1857 void
1858 SendToICSDelayed(s,msdelay)
1859      char *s;
1860      long msdelay;
1861 {
1862     int count, outCount, outError;
1863
1864     if (icsPR == NULL) return;
1865
1866     count = strlen(s);
1867     if (appData.debugMode) {
1868         fprintf(debugFP, ">ICS: ");
1869         show_bytes(debugFP, s, count);
1870         fprintf(debugFP, "\n");
1871     }
1872     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1873                                       msdelay);
1874     if (outCount < count) {
1875         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1876     }
1877 }
1878
1879
1880 /* Remove all highlighting escape sequences in s
1881    Also deletes any suffix starting with '('
1882    */
1883 char *
1884 StripHighlightAndTitle(s)
1885      char *s;
1886 {
1887     static char retbuf[MSG_SIZ];
1888     char *p = retbuf;
1889
1890     while (*s != NULLCHAR) {
1891         while (*s == '\033') {
1892             while (*s != NULLCHAR && !isalpha(*s)) s++;
1893             if (*s != NULLCHAR) s++;
1894         }
1895         while (*s != NULLCHAR && *s != '\033') {
1896             if (*s == '(' || *s == '[') {
1897                 *p = NULLCHAR;
1898                 return retbuf;
1899             }
1900             *p++ = *s++;
1901         }
1902     }
1903     *p = NULLCHAR;
1904     return retbuf;
1905 }
1906
1907 /* Remove all highlighting escape sequences in s */
1908 char *
1909 StripHighlight(s)
1910      char *s;
1911 {
1912     static char retbuf[MSG_SIZ];
1913     char *p = retbuf;
1914
1915     while (*s != NULLCHAR) {
1916         while (*s == '\033') {
1917             while (*s != NULLCHAR && !isalpha(*s)) s++;
1918             if (*s != NULLCHAR) s++;
1919         }
1920         while (*s != NULLCHAR && *s != '\033') {
1921             *p++ = *s++;
1922         }
1923     }
1924     *p = NULLCHAR;
1925     return retbuf;
1926 }
1927
1928 char *variantNames[] = VARIANT_NAMES;
1929 char *
1930 VariantName(v)
1931      VariantClass v;
1932 {
1933     return variantNames[v];
1934 }
1935
1936
1937 /* Identify a variant from the strings the chess servers use or the
1938    PGN Variant tag names we use. */
1939 VariantClass
1940 StringToVariant(e)
1941      char *e;
1942 {
1943     char *p;
1944     int wnum = -1;
1945     VariantClass v = VariantNormal;
1946     int i, found = FALSE;
1947     char buf[MSG_SIZ];
1948     int len;
1949
1950     if (!e) return v;
1951
1952     /* [HGM] skip over optional board-size prefixes */
1953     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1954         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1955         while( *e++ != '_');
1956     }
1957
1958     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1959         v = VariantNormal;
1960         found = TRUE;
1961     } else
1962     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1963       if (StrCaseStr(e, variantNames[i])) {
1964         v = (VariantClass) i;
1965         found = TRUE;
1966         break;
1967       }
1968     }
1969
1970     if (!found) {
1971       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1972           || StrCaseStr(e, "wild/fr")
1973           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1974         v = VariantFischeRandom;
1975       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1976                  (i = 1, p = StrCaseStr(e, "w"))) {
1977         p += i;
1978         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1979         if (isdigit(*p)) {
1980           wnum = atoi(p);
1981         } else {
1982           wnum = -1;
1983         }
1984         switch (wnum) {
1985         case 0: /* FICS only, actually */
1986         case 1:
1987           /* Castling legal even if K starts on d-file */
1988           v = VariantWildCastle;
1989           break;
1990         case 2:
1991         case 3:
1992         case 4:
1993           /* Castling illegal even if K & R happen to start in
1994              normal positions. */
1995           v = VariantNoCastle;
1996           break;
1997         case 5:
1998         case 7:
1999         case 8:
2000         case 10:
2001         case 11:
2002         case 12:
2003         case 13:
2004         case 14:
2005         case 15:
2006         case 18:
2007         case 19:
2008           /* Castling legal iff K & R start in normal positions */
2009           v = VariantNormal;
2010           break;
2011         case 6:
2012         case 20:
2013         case 21:
2014           /* Special wilds for position setup; unclear what to do here */
2015           v = VariantLoadable;
2016           break;
2017         case 9:
2018           /* Bizarre ICC game */
2019           v = VariantTwoKings;
2020           break;
2021         case 16:
2022           v = VariantKriegspiel;
2023           break;
2024         case 17:
2025           v = VariantLosers;
2026           break;
2027         case 22:
2028           v = VariantFischeRandom;
2029           break;
2030         case 23:
2031           v = VariantCrazyhouse;
2032           break;
2033         case 24:
2034           v = VariantBughouse;
2035           break;
2036         case 25:
2037           v = Variant3Check;
2038           break;
2039         case 26:
2040           /* Not quite the same as FICS suicide! */
2041           v = VariantGiveaway;
2042           break;
2043         case 27:
2044           v = VariantAtomic;
2045           break;
2046         case 28:
2047           v = VariantShatranj;
2048           break;
2049
2050         /* Temporary names for future ICC types.  The name *will* change in
2051            the next xboard/WinBoard release after ICC defines it. */
2052         case 29:
2053           v = Variant29;
2054           break;
2055         case 30:
2056           v = Variant30;
2057           break;
2058         case 31:
2059           v = Variant31;
2060           break;
2061         case 32:
2062           v = Variant32;
2063           break;
2064         case 33:
2065           v = Variant33;
2066           break;
2067         case 34:
2068           v = Variant34;
2069           break;
2070         case 35:
2071           v = Variant35;
2072           break;
2073         case 36:
2074           v = Variant36;
2075           break;
2076         case 37:
2077           v = VariantShogi;
2078           break;
2079         case 38:
2080           v = VariantXiangqi;
2081           break;
2082         case 39:
2083           v = VariantCourier;
2084           break;
2085         case 40:
2086           v = VariantGothic;
2087           break;
2088         case 41:
2089           v = VariantCapablanca;
2090           break;
2091         case 42:
2092           v = VariantKnightmate;
2093           break;
2094         case 43:
2095           v = VariantFairy;
2096           break;
2097         case 44:
2098           v = VariantCylinder;
2099           break;
2100         case 45:
2101           v = VariantFalcon;
2102           break;
2103         case 46:
2104           v = VariantCapaRandom;
2105           break;
2106         case 47:
2107           v = VariantBerolina;
2108           break;
2109         case 48:
2110           v = VariantJanus;
2111           break;
2112         case 49:
2113           v = VariantSuper;
2114           break;
2115         case 50:
2116           v = VariantGreat;
2117           break;
2118         case -1:
2119           /* Found "wild" or "w" in the string but no number;
2120              must assume it's normal chess. */
2121           v = VariantNormal;
2122           break;
2123         default:
2124           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2125           if( (len > MSG_SIZ) && appData.debugMode )
2126             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2127
2128           DisplayError(buf, 0);
2129           v = VariantUnknown;
2130           break;
2131         }
2132       }
2133     }
2134     if (appData.debugMode) {
2135       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2136               e, wnum, VariantName(v));
2137     }
2138     return v;
2139 }
2140
2141 static int leftover_start = 0, leftover_len = 0;
2142 char star_match[STAR_MATCH_N][MSG_SIZ];
2143
2144 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2145    advance *index beyond it, and set leftover_start to the new value of
2146    *index; else return FALSE.  If pattern contains the character '*', it
2147    matches any sequence of characters not containing '\r', '\n', or the
2148    character following the '*' (if any), and the matched sequence(s) are
2149    copied into star_match.
2150    */
2151 int
2152 looking_at(buf, index, pattern)
2153      char *buf;
2154      int *index;
2155      char *pattern;
2156 {
2157     char *bufp = &buf[*index], *patternp = pattern;
2158     int star_count = 0;
2159     char *matchp = star_match[0];
2160
2161     for (;;) {
2162         if (*patternp == NULLCHAR) {
2163             *index = leftover_start = bufp - buf;
2164             *matchp = NULLCHAR;
2165             return TRUE;
2166         }
2167         if (*bufp == NULLCHAR) return FALSE;
2168         if (*patternp == '*') {
2169             if (*bufp == *(patternp + 1)) {
2170                 *matchp = NULLCHAR;
2171                 matchp = star_match[++star_count];
2172                 patternp += 2;
2173                 bufp++;
2174                 continue;
2175             } else if (*bufp == '\n' || *bufp == '\r') {
2176                 patternp++;
2177                 if (*patternp == NULLCHAR)
2178                   continue;
2179                 else
2180                   return FALSE;
2181             } else {
2182                 *matchp++ = *bufp++;
2183                 continue;
2184             }
2185         }
2186         if (*patternp != *bufp) return FALSE;
2187         patternp++;
2188         bufp++;
2189     }
2190 }
2191
2192 void
2193 SendToPlayer(data, length)
2194      char *data;
2195      int length;
2196 {
2197     int error, outCount;
2198     outCount = OutputToProcess(NoProc, data, length, &error);
2199     if (outCount < length) {
2200         DisplayFatalError(_("Error writing to display"), error, 1);
2201     }
2202 }
2203
2204 void
2205 PackHolding(packed, holding)
2206      char packed[];
2207      char *holding;
2208 {
2209     char *p = holding;
2210     char *q = packed;
2211     int runlength = 0;
2212     int curr = 9999;
2213     do {
2214         if (*p == curr) {
2215             runlength++;
2216         } else {
2217             switch (runlength) {
2218               case 0:
2219                 break;
2220               case 1:
2221                 *q++ = curr;
2222                 break;
2223               case 2:
2224                 *q++ = curr;
2225                 *q++ = curr;
2226                 break;
2227               default:
2228                 sprintf(q, "%d", runlength);
2229                 while (*q) q++;
2230                 *q++ = curr;
2231                 break;
2232             }
2233             runlength = 1;
2234             curr = *p;
2235         }
2236     } while (*p++);
2237     *q = NULLCHAR;
2238 }
2239
2240 /* Telnet protocol requests from the front end */
2241 void
2242 TelnetRequest(ddww, option)
2243      unsigned char ddww, option;
2244 {
2245     unsigned char msg[3];
2246     int outCount, outError;
2247
2248     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2249
2250     if (appData.debugMode) {
2251         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2252         switch (ddww) {
2253           case TN_DO:
2254             ddwwStr = "DO";
2255             break;
2256           case TN_DONT:
2257             ddwwStr = "DONT";
2258             break;
2259           case TN_WILL:
2260             ddwwStr = "WILL";
2261             break;
2262           case TN_WONT:
2263             ddwwStr = "WONT";
2264             break;
2265           default:
2266             ddwwStr = buf1;
2267             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2268             break;
2269         }
2270         switch (option) {
2271           case TN_ECHO:
2272             optionStr = "ECHO";
2273             break;
2274           default:
2275             optionStr = buf2;
2276             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2277             break;
2278         }
2279         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2280     }
2281     msg[0] = TN_IAC;
2282     msg[1] = ddww;
2283     msg[2] = option;
2284     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2285     if (outCount < 3) {
2286         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2287     }
2288 }
2289
2290 void
2291 DoEcho()
2292 {
2293     if (!appData.icsActive) return;
2294     TelnetRequest(TN_DO, TN_ECHO);
2295 }
2296
2297 void
2298 DontEcho()
2299 {
2300     if (!appData.icsActive) return;
2301     TelnetRequest(TN_DONT, TN_ECHO);
2302 }
2303
2304 void
2305 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2306 {
2307     /* put the holdings sent to us by the server on the board holdings area */
2308     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2309     char p;
2310     ChessSquare piece;
2311
2312     if(gameInfo.holdingsWidth < 2)  return;
2313     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2314         return; // prevent overwriting by pre-board holdings
2315
2316     if( (int)lowestPiece >= BlackPawn ) {
2317         holdingsColumn = 0;
2318         countsColumn = 1;
2319         holdingsStartRow = BOARD_HEIGHT-1;
2320         direction = -1;
2321     } else {
2322         holdingsColumn = BOARD_WIDTH-1;
2323         countsColumn = BOARD_WIDTH-2;
2324         holdingsStartRow = 0;
2325         direction = 1;
2326     }
2327
2328     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2329         board[i][holdingsColumn] = EmptySquare;
2330         board[i][countsColumn]   = (ChessSquare) 0;
2331     }
2332     while( (p=*holdings++) != NULLCHAR ) {
2333         piece = CharToPiece( ToUpper(p) );
2334         if(piece == EmptySquare) continue;
2335         /*j = (int) piece - (int) WhitePawn;*/
2336         j = PieceToNumber(piece);
2337         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2338         if(j < 0) continue;               /* should not happen */
2339         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2340         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2341         board[holdingsStartRow+j*direction][countsColumn]++;
2342     }
2343 }
2344
2345
2346 void
2347 VariantSwitch(Board board, VariantClass newVariant)
2348 {
2349    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2350    static Board oldBoard;
2351
2352    startedFromPositionFile = FALSE;
2353    if(gameInfo.variant == newVariant) return;
2354
2355    /* [HGM] This routine is called each time an assignment is made to
2356     * gameInfo.variant during a game, to make sure the board sizes
2357     * are set to match the new variant. If that means adding or deleting
2358     * holdings, we shift the playing board accordingly
2359     * This kludge is needed because in ICS observe mode, we get boards
2360     * of an ongoing game without knowing the variant, and learn about the
2361     * latter only later. This can be because of the move list we requested,
2362     * in which case the game history is refilled from the beginning anyway,
2363     * but also when receiving holdings of a crazyhouse game. In the latter
2364     * case we want to add those holdings to the already received position.
2365     */
2366
2367
2368    if (appData.debugMode) {
2369      fprintf(debugFP, "Switch board from %s to %s\n",
2370              VariantName(gameInfo.variant), VariantName(newVariant));
2371      setbuf(debugFP, NULL);
2372    }
2373    shuffleOpenings = 0;       /* [HGM] shuffle */
2374    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2375    switch(newVariant)
2376      {
2377      case VariantShogi:
2378        newWidth = 9;  newHeight = 9;
2379        gameInfo.holdingsSize = 7;
2380      case VariantBughouse:
2381      case VariantCrazyhouse:
2382        newHoldingsWidth = 2; break;
2383      case VariantGreat:
2384        newWidth = 10;
2385      case VariantSuper:
2386        newHoldingsWidth = 2;
2387        gameInfo.holdingsSize = 8;
2388        break;
2389      case VariantGothic:
2390      case VariantCapablanca:
2391      case VariantCapaRandom:
2392        newWidth = 10;
2393      default:
2394        newHoldingsWidth = gameInfo.holdingsSize = 0;
2395      };
2396
2397    if(newWidth  != gameInfo.boardWidth  ||
2398       newHeight != gameInfo.boardHeight ||
2399       newHoldingsWidth != gameInfo.holdingsWidth ) {
2400
2401      /* shift position to new playing area, if needed */
2402      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2403        for(i=0; i<BOARD_HEIGHT; i++)
2404          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2405            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2406              board[i][j];
2407        for(i=0; i<newHeight; i++) {
2408          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2409          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2410        }
2411      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2412        for(i=0; i<BOARD_HEIGHT; i++)
2413          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2414            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2415              board[i][j];
2416      }
2417      gameInfo.boardWidth  = newWidth;
2418      gameInfo.boardHeight = newHeight;
2419      gameInfo.holdingsWidth = newHoldingsWidth;
2420      gameInfo.variant = newVariant;
2421      InitDrawingSizes(-2, 0);
2422    } else gameInfo.variant = newVariant;
2423    CopyBoard(oldBoard, board);   // remember correctly formatted board
2424      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2425    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2426 }
2427
2428 static int loggedOn = FALSE;
2429
2430 /*-- Game start info cache: --*/
2431 int gs_gamenum;
2432 char gs_kind[MSG_SIZ];
2433 static char player1Name[128] = "";
2434 static char player2Name[128] = "";
2435 static char cont_seq[] = "\n\\   ";
2436 static int player1Rating = -1;
2437 static int player2Rating = -1;
2438 /*----------------------------*/
2439
2440 ColorClass curColor = ColorNormal;
2441 int suppressKibitz = 0;
2442
2443 // [HGM] seekgraph
2444 Boolean soughtPending = FALSE;
2445 Boolean seekGraphUp;
2446 #define MAX_SEEK_ADS 200
2447 #define SQUARE 0x80
2448 char *seekAdList[MAX_SEEK_ADS];
2449 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2450 float tcList[MAX_SEEK_ADS];
2451 char colorList[MAX_SEEK_ADS];
2452 int nrOfSeekAds = 0;
2453 int minRating = 1010, maxRating = 2800;
2454 int hMargin = 10, vMargin = 20, h, w;
2455 extern int squareSize, lineGap;
2456
2457 void
2458 PlotSeekAd(int i)
2459 {
2460         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2461         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2462         if(r < minRating+100 && r >=0 ) r = minRating+100;
2463         if(r > maxRating) r = maxRating;
2464         if(tc < 1.) tc = 1.;
2465         if(tc > 95.) tc = 95.;
2466         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2467         y = ((double)r - minRating)/(maxRating - minRating)
2468             * (h-vMargin-squareSize/8-1) + vMargin;
2469         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2470         if(strstr(seekAdList[i], " u ")) color = 1;
2471         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2472            !strstr(seekAdList[i], "bullet") &&
2473            !strstr(seekAdList[i], "blitz") &&
2474            !strstr(seekAdList[i], "standard") ) color = 2;
2475         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2476         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2477 }
2478
2479 void
2480 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2481 {
2482         char buf[MSG_SIZ], *ext = "";
2483         VariantClass v = StringToVariant(type);
2484         if(strstr(type, "wild")) {
2485             ext = type + 4; // append wild number
2486             if(v == VariantFischeRandom) type = "chess960"; else
2487             if(v == VariantLoadable) type = "setup"; else
2488             type = VariantName(v);
2489         }
2490         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2491         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2492             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2493             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2494             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2495             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2496             seekNrList[nrOfSeekAds] = nr;
2497             zList[nrOfSeekAds] = 0;
2498             seekAdList[nrOfSeekAds++] = StrSave(buf);
2499             if(plot) PlotSeekAd(nrOfSeekAds-1);
2500         }
2501 }
2502
2503 void
2504 EraseSeekDot(int i)
2505 {
2506     int x = xList[i], y = yList[i], d=squareSize/4, k;
2507     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2508     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2509     // now replot every dot that overlapped
2510     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2511         int xx = xList[k], yy = yList[k];
2512         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2513             DrawSeekDot(xx, yy, colorList[k]);
2514     }
2515 }
2516
2517 void
2518 RemoveSeekAd(int nr)
2519 {
2520         int i;
2521         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2522             EraseSeekDot(i);
2523             if(seekAdList[i]) free(seekAdList[i]);
2524             seekAdList[i] = seekAdList[--nrOfSeekAds];
2525             seekNrList[i] = seekNrList[nrOfSeekAds];
2526             ratingList[i] = ratingList[nrOfSeekAds];
2527             colorList[i]  = colorList[nrOfSeekAds];
2528             tcList[i] = tcList[nrOfSeekAds];
2529             xList[i]  = xList[nrOfSeekAds];
2530             yList[i]  = yList[nrOfSeekAds];
2531             zList[i]  = zList[nrOfSeekAds];
2532             seekAdList[nrOfSeekAds] = NULL;
2533             break;
2534         }
2535 }
2536
2537 Boolean
2538 MatchSoughtLine(char *line)
2539 {
2540     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2541     int nr, base, inc, u=0; char dummy;
2542
2543     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2544        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2545        (u=1) &&
2546        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2547         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2548         // match: compact and save the line
2549         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2550         return TRUE;
2551     }
2552     return FALSE;
2553 }
2554
2555 int
2556 DrawSeekGraph()
2557 {
2558     int i;
2559     if(!seekGraphUp) return FALSE;
2560     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2561     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2562
2563     DrawSeekBackground(0, 0, w, h);
2564     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2565     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2566     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2567         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2568         yy = h-1-yy;
2569         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2570         if(i%500 == 0) {
2571             char buf[MSG_SIZ];
2572             snprintf(buf, MSG_SIZ, "%d", i);
2573             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2574         }
2575     }
2576     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2577     for(i=1; i<100; i+=(i<10?1:5)) {
2578         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2579         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2580         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2581             char buf[MSG_SIZ];
2582             snprintf(buf, MSG_SIZ, "%d", i);
2583             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2584         }
2585     }
2586     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2587     return TRUE;
2588 }
2589
2590 int SeekGraphClick(ClickType click, int x, int y, int moving)
2591 {
2592     static int lastDown = 0, displayed = 0, lastSecond;
2593     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2594         if(click == Release || moving) return FALSE;
2595         nrOfSeekAds = 0;
2596         soughtPending = TRUE;
2597         SendToICS(ics_prefix);
2598         SendToICS("sought\n"); // should this be "sought all"?
2599     } else { // issue challenge based on clicked ad
2600         int dist = 10000; int i, closest = 0, second = 0;
2601         for(i=0; i<nrOfSeekAds; i++) {
2602             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2603             if(d < dist) { dist = d; closest = i; }
2604             second += (d - zList[i] < 120); // count in-range ads
2605             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2606         }
2607         if(dist < 120) {
2608             char buf[MSG_SIZ];
2609             second = (second > 1);
2610             if(displayed != closest || second != lastSecond) {
2611                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2612                 lastSecond = second; displayed = closest;
2613             }
2614             if(click == Press) {
2615                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2616                 lastDown = closest;
2617                 return TRUE;
2618             } // on press 'hit', only show info
2619             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2620             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2621             SendToICS(ics_prefix);
2622             SendToICS(buf);
2623             return TRUE; // let incoming board of started game pop down the graph
2624         } else if(click == Release) { // release 'miss' is ignored
2625             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2626             if(moving == 2) { // right up-click
2627                 nrOfSeekAds = 0; // refresh graph
2628                 soughtPending = TRUE;
2629                 SendToICS(ics_prefix);
2630                 SendToICS("sought\n"); // should this be "sought all"?
2631             }
2632             return TRUE;
2633         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2634         // press miss or release hit 'pop down' seek graph
2635         seekGraphUp = FALSE;
2636         DrawPosition(TRUE, NULL);
2637     }
2638     return TRUE;
2639 }
2640
2641 void
2642 read_from_ics(isr, closure, data, count, error)
2643      InputSourceRef isr;
2644      VOIDSTAR closure;
2645      char *data;
2646      int count;
2647      int error;
2648 {
2649 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2650 #define STARTED_NONE 0
2651 #define STARTED_MOVES 1
2652 #define STARTED_BOARD 2
2653 #define STARTED_OBSERVE 3
2654 #define STARTED_HOLDINGS 4
2655 #define STARTED_CHATTER 5
2656 #define STARTED_COMMENT 6
2657 #define STARTED_MOVES_NOHIDE 7
2658
2659     static int started = STARTED_NONE;
2660     static char parse[20000];
2661     static int parse_pos = 0;
2662     static char buf[BUF_SIZE + 1];
2663     static int firstTime = TRUE, intfSet = FALSE;
2664     static ColorClass prevColor = ColorNormal;
2665     static int savingComment = FALSE;
2666     static int cmatch = 0; // continuation sequence match
2667     char *bp;
2668     char str[MSG_SIZ];
2669     int i, oldi;
2670     int buf_len;
2671     int next_out;
2672     int tkind;
2673     int backup;    /* [DM] For zippy color lines */
2674     char *p;
2675     char talker[MSG_SIZ]; // [HGM] chat
2676     int channel;
2677
2678     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2679
2680     if (appData.debugMode) {
2681       if (!error) {
2682         fprintf(debugFP, "<ICS: ");
2683         show_bytes(debugFP, data, count);
2684         fprintf(debugFP, "\n");
2685       }
2686     }
2687
2688     if (appData.debugMode) { int f = forwardMostMove;
2689         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2690                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2691                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2692     }
2693     if (count > 0) {
2694         /* If last read ended with a partial line that we couldn't parse,
2695            prepend it to the new read and try again. */
2696         if (leftover_len > 0) {
2697             for (i=0; i<leftover_len; i++)
2698               buf[i] = buf[leftover_start + i];
2699         }
2700
2701     /* copy new characters into the buffer */
2702     bp = buf + leftover_len;
2703     buf_len=leftover_len;
2704     for (i=0; i<count; i++)
2705     {
2706         // ignore these
2707         if (data[i] == '\r')
2708             continue;
2709
2710         // join lines split by ICS?
2711         if (!appData.noJoin)
2712         {
2713             /*
2714                 Joining just consists of finding matches against the
2715                 continuation sequence, and discarding that sequence
2716                 if found instead of copying it.  So, until a match
2717                 fails, there's nothing to do since it might be the
2718                 complete sequence, and thus, something we don't want
2719                 copied.
2720             */
2721             if (data[i] == cont_seq[cmatch])
2722             {
2723                 cmatch++;
2724                 if (cmatch == strlen(cont_seq))
2725                 {
2726                     cmatch = 0; // complete match.  just reset the counter
2727
2728                     /*
2729                         it's possible for the ICS to not include the space
2730                         at the end of the last word, making our [correct]
2731                         join operation fuse two separate words.  the server
2732                         does this when the space occurs at the width setting.
2733                     */
2734                     if (!buf_len || buf[buf_len-1] != ' ')
2735                     {
2736                         *bp++ = ' ';
2737                         buf_len++;
2738                     }
2739                 }
2740                 continue;
2741             }
2742             else if (cmatch)
2743             {
2744                 /*
2745                     match failed, so we have to copy what matched before
2746                     falling through and copying this character.  In reality,
2747                     this will only ever be just the newline character, but
2748                     it doesn't hurt to be precise.
2749                 */
2750                 strncpy(bp, cont_seq, cmatch);
2751                 bp += cmatch;
2752                 buf_len += cmatch;
2753                 cmatch = 0;
2754             }
2755         }
2756
2757         // copy this char
2758         *bp++ = data[i];
2759         buf_len++;
2760     }
2761
2762         buf[buf_len] = NULLCHAR;
2763 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2764         next_out = 0;
2765         leftover_start = 0;
2766
2767         i = 0;
2768         while (i < buf_len) {
2769             /* Deal with part of the TELNET option negotiation
2770                protocol.  We refuse to do anything beyond the
2771                defaults, except that we allow the WILL ECHO option,
2772                which ICS uses to turn off password echoing when we are
2773                directly connected to it.  We reject this option
2774                if localLineEditing mode is on (always on in xboard)
2775                and we are talking to port 23, which might be a real
2776                telnet server that will try to keep WILL ECHO on permanently.
2777              */
2778             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2779                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2780                 unsigned char option;
2781                 oldi = i;
2782                 switch ((unsigned char) buf[++i]) {
2783                   case TN_WILL:
2784                     if (appData.debugMode)
2785                       fprintf(debugFP, "\n<WILL ");
2786                     switch (option = (unsigned char) buf[++i]) {
2787                       case TN_ECHO:
2788                         if (appData.debugMode)
2789                           fprintf(debugFP, "ECHO ");
2790                         /* Reply only if this is a change, according
2791                            to the protocol rules. */
2792                         if (remoteEchoOption) break;
2793                         if (appData.localLineEditing &&
2794                             atoi(appData.icsPort) == TN_PORT) {
2795                             TelnetRequest(TN_DONT, TN_ECHO);
2796                         } else {
2797                             EchoOff();
2798                             TelnetRequest(TN_DO, TN_ECHO);
2799                             remoteEchoOption = TRUE;
2800                         }
2801                         break;
2802                       default:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "%d ", option);
2805                         /* Whatever this is, we don't want it. */
2806                         TelnetRequest(TN_DONT, option);
2807                         break;
2808                     }
2809                     break;
2810                   case TN_WONT:
2811                     if (appData.debugMode)
2812                       fprintf(debugFP, "\n<WONT ");
2813                     switch (option = (unsigned char) buf[++i]) {
2814                       case TN_ECHO:
2815                         if (appData.debugMode)
2816                           fprintf(debugFP, "ECHO ");
2817                         /* Reply only if this is a change, according
2818                            to the protocol rules. */
2819                         if (!remoteEchoOption) break;
2820                         EchoOn();
2821                         TelnetRequest(TN_DONT, TN_ECHO);
2822                         remoteEchoOption = FALSE;
2823                         break;
2824                       default:
2825                         if (appData.debugMode)
2826                           fprintf(debugFP, "%d ", (unsigned char) option);
2827                         /* Whatever this is, it must already be turned
2828                            off, because we never agree to turn on
2829                            anything non-default, so according to the
2830                            protocol rules, we don't reply. */
2831                         break;
2832                     }
2833                     break;
2834                   case TN_DO:
2835                     if (appData.debugMode)
2836                       fprintf(debugFP, "\n<DO ");
2837                     switch (option = (unsigned char) buf[++i]) {
2838                       default:
2839                         /* Whatever this is, we refuse to do it. */
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", option);
2842                         TelnetRequest(TN_WONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_DONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<DONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       default:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "%d ", option);
2853                         /* Whatever this is, we are already not doing
2854                            it, because we never agree to do anything
2855                            non-default, so according to the protocol
2856                            rules, we don't reply. */
2857                         break;
2858                     }
2859                     break;
2860                   case TN_IAC:
2861                     if (appData.debugMode)
2862                       fprintf(debugFP, "\n<IAC ");
2863                     /* Doubled IAC; pass it through */
2864                     i--;
2865                     break;
2866                   default:
2867                     if (appData.debugMode)
2868                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2869                     /* Drop all other telnet commands on the floor */
2870                     break;
2871                 }
2872                 if (oldi > next_out)
2873                   SendToPlayer(&buf[next_out], oldi - next_out);
2874                 if (++i > next_out)
2875                   next_out = i;
2876                 continue;
2877             }
2878
2879             /* OK, this at least will *usually* work */
2880             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2881                 loggedOn = TRUE;
2882             }
2883
2884             if (loggedOn && !intfSet) {
2885                 if (ics_type == ICS_ICC) {
2886                   snprintf(str, MSG_SIZ,
2887                           "/set-quietly interface %s\n/set-quietly style 12\n",
2888                           programVersion);
2889                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2890                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2891                 } else if (ics_type == ICS_CHESSNET) {
2892                   snprintf(str, MSG_SIZ, "/style 12\n");
2893                 } else {
2894                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2895                   strcat(str, programVersion);
2896                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2897                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2898                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2899 #ifdef WIN32
2900                   strcat(str, "$iset nohighlight 1\n");
2901 #endif
2902                   strcat(str, "$iset lock 1\n$style 12\n");
2903                 }
2904                 SendToICS(str);
2905                 NotifyFrontendLogin();
2906                 intfSet = TRUE;
2907             }
2908
2909             if (started == STARTED_COMMENT) {
2910                 /* Accumulate characters in comment */
2911                 parse[parse_pos++] = buf[i];
2912                 if (buf[i] == '\n') {
2913                     parse[parse_pos] = NULLCHAR;
2914                     if(chattingPartner>=0) {
2915                         char mess[MSG_SIZ];
2916                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2917                         OutputChatMessage(chattingPartner, mess);
2918                         chattingPartner = -1;
2919                         next_out = i+1; // [HGM] suppress printing in ICS window
2920                     } else
2921                     if(!suppressKibitz) // [HGM] kibitz
2922                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2923                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2924                         int nrDigit = 0, nrAlph = 0, j;
2925                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2926                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2927                         parse[parse_pos] = NULLCHAR;
2928                         // try to be smart: if it does not look like search info, it should go to
2929                         // ICS interaction window after all, not to engine-output window.
2930                         for(j=0; j<parse_pos; j++) { // count letters and digits
2931                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2932                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2933                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2934                         }
2935                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2936                             int depth=0; float score;
2937                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2938                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2939                                 pvInfoList[forwardMostMove-1].depth = depth;
2940                                 pvInfoList[forwardMostMove-1].score = 100*score;
2941                             }
2942                             OutputKibitz(suppressKibitz, parse);
2943                         } else {
2944                             char tmp[MSG_SIZ];
2945                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2946                             SendToPlayer(tmp, strlen(tmp));
2947                         }
2948                         next_out = i+1; // [HGM] suppress printing in ICS window
2949                     }
2950                     started = STARTED_NONE;
2951                 } else {
2952                     /* Don't match patterns against characters in comment */
2953                     i++;
2954                     continue;
2955                 }
2956             }
2957             if (started == STARTED_CHATTER) {
2958                 if (buf[i] != '\n') {
2959                     /* Don't match patterns against characters in chatter */
2960                     i++;
2961                     continue;
2962                 }
2963                 started = STARTED_NONE;
2964                 if(suppressKibitz) next_out = i+1;
2965             }
2966
2967             /* Kludge to deal with rcmd protocol */
2968             if (firstTime && looking_at(buf, &i, "\001*")) {
2969                 DisplayFatalError(&buf[1], 0, 1);
2970                 continue;
2971             } else {
2972                 firstTime = FALSE;
2973             }
2974
2975             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2976                 ics_type = ICS_ICC;
2977                 ics_prefix = "/";
2978                 if (appData.debugMode)
2979                   fprintf(debugFP, "ics_type %d\n", ics_type);
2980                 continue;
2981             }
2982             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2983                 ics_type = ICS_FICS;
2984                 ics_prefix = "$";
2985                 if (appData.debugMode)
2986                   fprintf(debugFP, "ics_type %d\n", ics_type);
2987                 continue;
2988             }
2989             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2990                 ics_type = ICS_CHESSNET;
2991                 ics_prefix = "/";
2992                 if (appData.debugMode)
2993                   fprintf(debugFP, "ics_type %d\n", ics_type);
2994                 continue;
2995             }
2996
2997             if (!loggedOn &&
2998                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2999                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3000                  looking_at(buf, &i, "will be \"*\""))) {
3001               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3002               continue;
3003             }
3004
3005             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3006               char buf[MSG_SIZ];
3007               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3008               DisplayIcsInteractionTitle(buf);
3009               have_set_title = TRUE;
3010             }
3011
3012             /* skip finger notes */
3013             if (started == STARTED_NONE &&
3014                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3015                  (buf[i] == '1' && buf[i+1] == '0')) &&
3016                 buf[i+2] == ':' && buf[i+3] == ' ') {
3017               started = STARTED_CHATTER;
3018               i += 3;
3019               continue;
3020             }
3021
3022             oldi = i;
3023             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3024             if(appData.seekGraph) {
3025                 if(soughtPending && MatchSoughtLine(buf+i)) {
3026                     i = strstr(buf+i, "rated") - buf;
3027                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3028                     next_out = leftover_start = i;
3029                     started = STARTED_CHATTER;
3030                     suppressKibitz = TRUE;
3031                     continue;
3032                 }
3033                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3034                         && looking_at(buf, &i, "* ads displayed")) {
3035                     soughtPending = FALSE;
3036                     seekGraphUp = TRUE;
3037                     DrawSeekGraph();
3038                     continue;
3039                 }
3040                 if(appData.autoRefresh) {
3041                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3042                         int s = (ics_type == ICS_ICC); // ICC format differs
3043                         if(seekGraphUp)
3044                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3045                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3046                         looking_at(buf, &i, "*% "); // eat prompt
3047                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3048                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3049                         next_out = i; // suppress
3050                         continue;
3051                     }
3052                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3053                         char *p = star_match[0];
3054                         while(*p) {
3055                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3056                             while(*p && *p++ != ' '); // next
3057                         }
3058                         looking_at(buf, &i, "*% "); // eat prompt
3059                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3060                         next_out = i;
3061                         continue;
3062                     }
3063                 }
3064             }
3065
3066             /* skip formula vars */
3067             if (started == STARTED_NONE &&
3068                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3069               started = STARTED_CHATTER;
3070               i += 3;
3071               continue;
3072             }
3073
3074             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3075             if (appData.autoKibitz && started == STARTED_NONE &&
3076                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3077                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3078                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3079                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3080                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3081                         suppressKibitz = TRUE;
3082                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3083                         next_out = i;
3084                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3085                                 && (gameMode == IcsPlayingWhite)) ||
3086                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3087                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3088                             started = STARTED_CHATTER; // own kibitz we simply discard
3089                         else {
3090                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3091                             parse_pos = 0; parse[0] = NULLCHAR;
3092                             savingComment = TRUE;
3093                             suppressKibitz = gameMode != IcsObserving ? 2 :
3094                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3095                         }
3096                         continue;
3097                 } else
3098                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3099                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3100                          && atoi(star_match[0])) {
3101                     // suppress the acknowledgements of our own autoKibitz
3102                     char *p;
3103                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3104                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3105                     SendToPlayer(star_match[0], strlen(star_match[0]));
3106                     if(looking_at(buf, &i, "*% ")) // eat prompt
3107                         suppressKibitz = FALSE;
3108                     next_out = i;
3109                     continue;
3110                 }
3111             } // [HGM] kibitz: end of patch
3112
3113             // [HGM] chat: intercept tells by users for which we have an open chat window
3114             channel = -1;
3115             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3116                                            looking_at(buf, &i, "* whispers:") ||
3117                                            looking_at(buf, &i, "* kibitzes:") ||
3118                                            looking_at(buf, &i, "* shouts:") ||
3119                                            looking_at(buf, &i, "* c-shouts:") ||
3120                                            looking_at(buf, &i, "--> * ") ||
3121                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3122                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3123                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3124                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3125                 int p;
3126                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3127                 chattingPartner = -1;
3128
3129                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3130                 for(p=0; p<MAX_CHAT; p++) {
3131                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3132                     talker[0] = '['; strcat(talker, "] ");
3133                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3134                     chattingPartner = p; break;
3135                     }
3136                 } else
3137                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3138                 for(p=0; p<MAX_CHAT; p++) {
3139                     if(!strcmp("kibitzes", chatPartner[p])) {
3140                         talker[0] = '['; strcat(talker, "] ");
3141                         chattingPartner = p; break;
3142                     }
3143                 } else
3144                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3145                 for(p=0; p<MAX_CHAT; p++) {
3146                     if(!strcmp("whispers", chatPartner[p])) {
3147                         talker[0] = '['; strcat(talker, "] ");
3148                         chattingPartner = p; break;
3149                     }
3150                 } else
3151                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3152                   if(buf[i-8] == '-' && buf[i-3] == 't')
3153                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3154                     if(!strcmp("c-shouts", chatPartner[p])) {
3155                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3156                         chattingPartner = p; break;
3157                     }
3158                   }
3159                   if(chattingPartner < 0)
3160                   for(p=0; p<MAX_CHAT; p++) {
3161                     if(!strcmp("shouts", chatPartner[p])) {
3162                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3163                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3164                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3165                         chattingPartner = p; break;
3166                     }
3167                   }
3168                 }
3169                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3170                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3171                     talker[0] = 0; Colorize(ColorTell, FALSE);
3172                     chattingPartner = p; break;
3173                 }
3174                 if(chattingPartner<0) i = oldi; else {
3175                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3176                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3177                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3178                     started = STARTED_COMMENT;
3179                     parse_pos = 0; parse[0] = NULLCHAR;
3180                     savingComment = 3 + chattingPartner; // counts as TRUE
3181                     suppressKibitz = TRUE;
3182                     continue;
3183                 }
3184             } // [HGM] chat: end of patch
3185
3186           backup = i;
3187             if (appData.zippyTalk || appData.zippyPlay) {
3188                 /* [DM] Backup address for color zippy lines */
3189 #if ZIPPY
3190                if (loggedOn == TRUE)
3191                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3192                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3193 #endif
3194             } // [DM] 'else { ' deleted
3195                 if (
3196                     /* Regular tells and says */
3197                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3198                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3199                     looking_at(buf, &i, "* says: ") ||
3200                     /* Don't color "message" or "messages" output */
3201                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3202                     looking_at(buf, &i, "*. * at *:*: ") ||
3203                     looking_at(buf, &i, "--* (*:*): ") ||
3204                     /* Message notifications (same color as tells) */
3205                     looking_at(buf, &i, "* has left a message ") ||
3206                     looking_at(buf, &i, "* just sent you a message:\n") ||
3207                     /* Whispers and kibitzes */
3208                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3209                     looking_at(buf, &i, "* kibitzes: ") ||
3210                     /* Channel tells */
3211                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3212
3213                   if (tkind == 1 && strchr(star_match[0], ':')) {
3214                       /* Avoid "tells you:" spoofs in channels */
3215                      tkind = 3;
3216                   }
3217                   if (star_match[0][0] == NULLCHAR ||
3218                       strchr(star_match[0], ' ') ||
3219                       (tkind == 3 && strchr(star_match[1], ' '))) {
3220                     /* Reject bogus matches */
3221                     i = oldi;
3222                   } else {
3223                     if (appData.colorize) {
3224                       if (oldi > next_out) {
3225                         SendToPlayer(&buf[next_out], oldi - next_out);
3226                         next_out = oldi;
3227                       }
3228                       switch (tkind) {
3229                       case 1:
3230                         Colorize(ColorTell, FALSE);
3231                         curColor = ColorTell;
3232                         break;
3233                       case 2:
3234                         Colorize(ColorKibitz, FALSE);
3235                         curColor = ColorKibitz;
3236                         break;
3237                       case 3:
3238                         p = strrchr(star_match[1], '(');
3239                         if (p == NULL) {
3240                           p = star_match[1];
3241                         } else {
3242                           p++;
3243                         }
3244                         if (atoi(p) == 1) {
3245                           Colorize(ColorChannel1, FALSE);
3246                           curColor = ColorChannel1;
3247                         } else {
3248                           Colorize(ColorChannel, FALSE);
3249                           curColor = ColorChannel;
3250                         }
3251                         break;
3252                       case 5:
3253                         curColor = ColorNormal;
3254                         break;
3255                       }
3256                     }
3257                     if (started == STARTED_NONE && appData.autoComment &&
3258                         (gameMode == IcsObserving ||
3259                          gameMode == IcsPlayingWhite ||
3260                          gameMode == IcsPlayingBlack)) {
3261                       parse_pos = i - oldi;
3262                       memcpy(parse, &buf[oldi], parse_pos);
3263                       parse[parse_pos] = NULLCHAR;
3264                       started = STARTED_COMMENT;
3265                       savingComment = TRUE;
3266                     } else {
3267                       started = STARTED_CHATTER;
3268                       savingComment = FALSE;
3269                     }
3270                     loggedOn = TRUE;
3271                     continue;
3272                   }
3273                 }
3274
3275                 if (looking_at(buf, &i, "* s-shouts: ") ||
3276                     looking_at(buf, &i, "* c-shouts: ")) {
3277                     if (appData.colorize) {
3278                         if (oldi > next_out) {
3279                             SendToPlayer(&buf[next_out], oldi - next_out);
3280                             next_out = oldi;
3281                         }
3282                         Colorize(ColorSShout, FALSE);
3283                         curColor = ColorSShout;
3284                     }
3285                     loggedOn = TRUE;
3286                     started = STARTED_CHATTER;
3287                     continue;
3288                 }
3289
3290                 if (looking_at(buf, &i, "--->")) {
3291                     loggedOn = TRUE;
3292                     continue;
3293                 }
3294
3295                 if (looking_at(buf, &i, "* shouts: ") ||
3296                     looking_at(buf, &i, "--> ")) {
3297                     if (appData.colorize) {
3298                         if (oldi > next_out) {
3299                             SendToPlayer(&buf[next_out], oldi - next_out);
3300                             next_out = oldi;
3301                         }
3302                         Colorize(ColorShout, FALSE);
3303                         curColor = ColorShout;
3304                     }
3305                     loggedOn = TRUE;
3306                     started = STARTED_CHATTER;
3307                     continue;
3308                 }
3309
3310                 if (looking_at( buf, &i, "Challenge:")) {
3311                     if (appData.colorize) {
3312                         if (oldi > next_out) {
3313                             SendToPlayer(&buf[next_out], oldi - next_out);
3314                             next_out = oldi;
3315                         }
3316                         Colorize(ColorChallenge, FALSE);
3317                         curColor = ColorChallenge;
3318                     }
3319                     loggedOn = TRUE;
3320                     continue;
3321                 }
3322
3323                 if (looking_at(buf, &i, "* offers you") ||
3324                     looking_at(buf, &i, "* offers to be") ||
3325                     looking_at(buf, &i, "* would like to") ||
3326                     looking_at(buf, &i, "* requests to") ||
3327                     looking_at(buf, &i, "Your opponent offers") ||
3328                     looking_at(buf, &i, "Your opponent requests")) {
3329
3330                     if (appData.colorize) {
3331                         if (oldi > next_out) {
3332                             SendToPlayer(&buf[next_out], oldi - next_out);
3333                             next_out = oldi;
3334                         }
3335                         Colorize(ColorRequest, FALSE);
3336                         curColor = ColorRequest;
3337                     }
3338                     continue;
3339                 }
3340
3341                 if (looking_at(buf, &i, "* (*) seeking")) {
3342                     if (appData.colorize) {
3343                         if (oldi > next_out) {
3344                             SendToPlayer(&buf[next_out], oldi - next_out);
3345                             next_out = oldi;
3346                         }
3347                         Colorize(ColorSeek, FALSE);
3348                         curColor = ColorSeek;
3349                     }
3350                     continue;
3351             }
3352
3353           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3354
3355             if (looking_at(buf, &i, "\\   ")) {
3356                 if (prevColor != ColorNormal) {
3357                     if (oldi > next_out) {
3358                         SendToPlayer(&buf[next_out], oldi - next_out);
3359                         next_out = oldi;
3360                     }
3361                     Colorize(prevColor, TRUE);
3362                     curColor = prevColor;
3363                 }
3364                 if (savingComment) {
3365                     parse_pos = i - oldi;
3366                     memcpy(parse, &buf[oldi], parse_pos);
3367                     parse[parse_pos] = NULLCHAR;
3368                     started = STARTED_COMMENT;
3369                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3370                         chattingPartner = savingComment - 3; // kludge to remember the box
3371                 } else {
3372                     started = STARTED_CHATTER;
3373                 }
3374                 continue;
3375             }
3376
3377             if (looking_at(buf, &i, "Black Strength :") ||
3378                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3379                 looking_at(buf, &i, "<10>") ||
3380                 looking_at(buf, &i, "#@#")) {
3381                 /* Wrong board style */
3382                 loggedOn = TRUE;
3383                 SendToICS(ics_prefix);
3384                 SendToICS("set style 12\n");
3385                 SendToICS(ics_prefix);
3386                 SendToICS("refresh\n");
3387                 continue;
3388             }
3389
3390             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3391                 ICSInitScript();
3392                 have_sent_ICS_logon = 1;
3393                 continue;
3394             }
3395
3396             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3397                 (looking_at(buf, &i, "\n<12> ") ||
3398                  looking_at(buf, &i, "<12> "))) {
3399                 loggedOn = TRUE;
3400                 if (oldi > next_out) {
3401                     SendToPlayer(&buf[next_out], oldi - next_out);
3402                 }
3403                 next_out = i;
3404                 started = STARTED_BOARD;
3405                 parse_pos = 0;
3406                 continue;
3407             }
3408
3409             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3410                 looking_at(buf, &i, "<b1> ")) {
3411                 if (oldi > next_out) {
3412                     SendToPlayer(&buf[next_out], oldi - next_out);
3413                 }
3414                 next_out = i;
3415                 started = STARTED_HOLDINGS;
3416                 parse_pos = 0;
3417                 continue;
3418             }
3419
3420             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3421                 loggedOn = TRUE;
3422                 /* Header for a move list -- first line */
3423
3424                 switch (ics_getting_history) {
3425                   case H_FALSE:
3426                     switch (gameMode) {
3427                       case IcsIdle:
3428                       case BeginningOfGame:
3429                         /* User typed "moves" or "oldmoves" while we
3430                            were idle.  Pretend we asked for these
3431                            moves and soak them up so user can step
3432                            through them and/or save them.
3433                            */
3434                         Reset(FALSE, TRUE);
3435                         gameMode = IcsObserving;
3436                         ModeHighlight();
3437                         ics_gamenum = -1;
3438                         ics_getting_history = H_GOT_UNREQ_HEADER;
3439                         break;
3440                       case EditGame: /*?*/
3441                       case EditPosition: /*?*/
3442                         /* Should above feature work in these modes too? */
3443                         /* For now it doesn't */
3444                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3445                         break;
3446                       default:
3447                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3448                         break;
3449                     }
3450                     break;
3451                   case H_REQUESTED:
3452                     /* Is this the right one? */
3453                     if (gameInfo.white && gameInfo.black &&
3454                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3455                         strcmp(gameInfo.black, star_match[2]) == 0) {
3456                         /* All is well */
3457                         ics_getting_history = H_GOT_REQ_HEADER;
3458                     }
3459                     break;
3460                   case H_GOT_REQ_HEADER:
3461                   case H_GOT_UNREQ_HEADER:
3462                   case H_GOT_UNWANTED_HEADER:
3463                   case H_GETTING_MOVES:
3464                     /* Should not happen */
3465                     DisplayError(_("Error gathering move list: two headers"), 0);
3466                     ics_getting_history = H_FALSE;
3467                     break;
3468                 }
3469
3470                 /* Save player ratings into gameInfo if needed */
3471                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3472                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3473                     (gameInfo.whiteRating == -1 ||
3474                      gameInfo.blackRating == -1)) {
3475
3476                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3477                     gameInfo.blackRating = string_to_rating(star_match[3]);
3478                     if (appData.debugMode)
3479                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3480                               gameInfo.whiteRating, gameInfo.blackRating);
3481                 }
3482                 continue;
3483             }
3484
3485             if (looking_at(buf, &i,
3486               "* * match, initial time: * minute*, increment: * second")) {
3487                 /* Header for a move list -- second line */
3488                 /* Initial board will follow if this is a wild game */
3489                 if (gameInfo.event != NULL) free(gameInfo.event);
3490                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3491                 gameInfo.event = StrSave(str);
3492                 /* [HGM] we switched variant. Translate boards if needed. */
3493                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3494                 continue;
3495             }
3496
3497             if (looking_at(buf, &i, "Move  ")) {
3498                 /* Beginning of a move list */
3499                 switch (ics_getting_history) {
3500                   case H_FALSE:
3501                     /* Normally should not happen */
3502                     /* Maybe user hit reset while we were parsing */
3503                     break;
3504                   case H_REQUESTED:
3505                     /* Happens if we are ignoring a move list that is not
3506                      * the one we just requested.  Common if the user
3507                      * tries to observe two games without turning off
3508                      * getMoveList */
3509                     break;
3510                   case H_GETTING_MOVES:
3511                     /* Should not happen */
3512                     DisplayError(_("Error gathering move list: nested"), 0);
3513                     ics_getting_history = H_FALSE;
3514                     break;
3515                   case H_GOT_REQ_HEADER:
3516                     ics_getting_history = H_GETTING_MOVES;
3517                     started = STARTED_MOVES;
3518                     parse_pos = 0;
3519                     if (oldi > next_out) {
3520                         SendToPlayer(&buf[next_out], oldi - next_out);
3521                     }
3522                     break;
3523                   case H_GOT_UNREQ_HEADER:
3524                     ics_getting_history = H_GETTING_MOVES;
3525                     started = STARTED_MOVES_NOHIDE;
3526                     parse_pos = 0;
3527                     break;
3528                   case H_GOT_UNWANTED_HEADER:
3529                     ics_getting_history = H_FALSE;
3530                     break;
3531                 }
3532                 continue;
3533             }
3534
3535             if (looking_at(buf, &i, "% ") ||
3536                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3537                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3538                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3539                     soughtPending = FALSE;
3540                     seekGraphUp = TRUE;
3541                     DrawSeekGraph();
3542                 }
3543                 if(suppressKibitz) next_out = i;
3544                 savingComment = FALSE;
3545                 suppressKibitz = 0;
3546                 switch (started) {
3547                   case STARTED_MOVES:
3548                   case STARTED_MOVES_NOHIDE:
3549                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3550                     parse[parse_pos + i - oldi] = NULLCHAR;
3551                     ParseGameHistory(parse);
3552 #if ZIPPY
3553                     if (appData.zippyPlay && first.initDone) {
3554                         FeedMovesToProgram(&first, forwardMostMove);
3555                         if (gameMode == IcsPlayingWhite) {
3556                             if (WhiteOnMove(forwardMostMove)) {
3557                                 if (first.sendTime) {
3558                                   if (first.useColors) {
3559                                     SendToProgram("black\n", &first);
3560                                   }
3561                                   SendTimeRemaining(&first, TRUE);
3562                                 }
3563                                 if (first.useColors) {
3564                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3565                                 }
3566                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3567                                 first.maybeThinking = TRUE;
3568                             } else {
3569                                 if (first.usePlayother) {
3570                                   if (first.sendTime) {
3571                                     SendTimeRemaining(&first, TRUE);
3572                                   }
3573                                   SendToProgram("playother\n", &first);
3574                                   firstMove = FALSE;
3575                                 } else {
3576                                   firstMove = TRUE;
3577                                 }
3578                             }
3579                         } else if (gameMode == IcsPlayingBlack) {
3580                             if (!WhiteOnMove(forwardMostMove)) {
3581                                 if (first.sendTime) {
3582                                   if (first.useColors) {
3583                                     SendToProgram("white\n", &first);
3584                                   }
3585                                   SendTimeRemaining(&first, FALSE);
3586                                 }
3587                                 if (first.useColors) {
3588                                   SendToProgram("black\n", &first);
3589                                 }
3590                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3591                                 first.maybeThinking = TRUE;
3592                             } else {
3593                                 if (first.usePlayother) {
3594                                   if (first.sendTime) {
3595                                     SendTimeRemaining(&first, FALSE);
3596                                   }
3597                                   SendToProgram("playother\n", &first);
3598                                   firstMove = FALSE;
3599                                 } else {
3600                                   firstMove = TRUE;
3601                                 }
3602                             }
3603                         }
3604                     }
3605 #endif
3606                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3607                         /* Moves came from oldmoves or moves command
3608                            while we weren't doing anything else.
3609                            */
3610                         currentMove = forwardMostMove;
3611                         ClearHighlights();/*!!could figure this out*/
3612                         flipView = appData.flipView;
3613                         DrawPosition(TRUE, boards[currentMove]);
3614                         DisplayBothClocks();
3615                         snprintf(str, MSG_SIZ, "%s vs. %s",
3616                                 gameInfo.white, gameInfo.black);
3617                         DisplayTitle(str);
3618                         gameMode = IcsIdle;
3619                     } else {
3620                         /* Moves were history of an active game */
3621                         if (gameInfo.resultDetails != NULL) {
3622                             free(gameInfo.resultDetails);
3623                             gameInfo.resultDetails = NULL;
3624                         }
3625                     }
3626                     HistorySet(parseList, backwardMostMove,
3627                                forwardMostMove, currentMove-1);
3628                     DisplayMove(currentMove - 1);
3629                     if (started == STARTED_MOVES) next_out = i;
3630                     started = STARTED_NONE;
3631                     ics_getting_history = H_FALSE;
3632                     break;
3633
3634                   case STARTED_OBSERVE:
3635                     started = STARTED_NONE;
3636                     SendToICS(ics_prefix);
3637                     SendToICS("refresh\n");
3638                     break;
3639
3640                   default:
3641                     break;
3642                 }
3643                 if(bookHit) { // [HGM] book: simulate book reply
3644                     static char bookMove[MSG_SIZ]; // a bit generous?
3645
3646                     programStats.nodes = programStats.depth = programStats.time =
3647                     programStats.score = programStats.got_only_move = 0;
3648                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3649
3650                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3651                     strcat(bookMove, bookHit);
3652                     HandleMachineMove(bookMove, &first);
3653                 }
3654                 continue;
3655             }
3656
3657             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3658                  started == STARTED_HOLDINGS ||
3659                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3660                 /* Accumulate characters in move list or board */
3661                 parse[parse_pos++] = buf[i];
3662             }
3663
3664             /* Start of game messages.  Mostly we detect start of game
3665                when the first board image arrives.  On some versions
3666                of the ICS, though, we need to do a "refresh" after starting
3667                to observe in order to get the current board right away. */
3668             if (looking_at(buf, &i, "Adding game * to observation list")) {
3669                 started = STARTED_OBSERVE;
3670                 continue;
3671             }
3672
3673             /* Handle auto-observe */
3674             if (appData.autoObserve &&
3675                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3676                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3677                 char *player;
3678                 /* Choose the player that was highlighted, if any. */
3679                 if (star_match[0][0] == '\033' ||
3680                     star_match[1][0] != '\033') {
3681                     player = star_match[0];
3682                 } else {
3683                     player = star_match[2];
3684                 }
3685                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3686                         ics_prefix, StripHighlightAndTitle(player));
3687                 SendToICS(str);
3688
3689                 /* Save ratings from notify string */
3690                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3691                 player1Rating = string_to_rating(star_match[1]);
3692                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3693                 player2Rating = string_to_rating(star_match[3]);
3694
3695                 if (appData.debugMode)
3696                   fprintf(debugFP,
3697                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3698                           player1Name, player1Rating,
3699                           player2Name, player2Rating);
3700
3701                 continue;
3702             }
3703
3704             /* Deal with automatic examine mode after a game,
3705                and with IcsObserving -> IcsExamining transition */
3706             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3707                 looking_at(buf, &i, "has made you an examiner of game *")) {
3708
3709                 int gamenum = atoi(star_match[0]);
3710                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3711                     gamenum == ics_gamenum) {
3712                     /* We were already playing or observing this game;
3713                        no need to refetch history */
3714                     gameMode = IcsExamining;
3715                     if (pausing) {
3716                         pauseExamForwardMostMove = forwardMostMove;
3717                     } else if (currentMove < forwardMostMove) {
3718                         ForwardInner(forwardMostMove);
3719                     }
3720                 } else {
3721                     /* I don't think this case really can happen */
3722                     SendToICS(ics_prefix);
3723                     SendToICS("refresh\n");
3724                 }
3725                 continue;
3726             }
3727
3728             /* Error messages */
3729 //          if (ics_user_moved) {
3730             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3731                 if (looking_at(buf, &i, "Illegal move") ||
3732                     looking_at(buf, &i, "Not a legal move") ||
3733                     looking_at(buf, &i, "Your king is in check") ||
3734                     looking_at(buf, &i, "It isn't your turn") ||
3735                     looking_at(buf, &i, "It is not your move")) {
3736                     /* Illegal move */
3737                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3738                         currentMove = forwardMostMove-1;
3739                         DisplayMove(currentMove - 1); /* before DMError */
3740                         DrawPosition(FALSE, boards[currentMove]);
3741                         SwitchClocks(forwardMostMove-1); // [HGM] race
3742                         DisplayBothClocks();
3743                     }
3744                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3745                     ics_user_moved = 0;
3746                     continue;
3747                 }
3748             }
3749
3750             if (looking_at(buf, &i, "still have time") ||
3751                 looking_at(buf, &i, "not out of time") ||
3752                 looking_at(buf, &i, "either player is out of time") ||
3753                 looking_at(buf, &i, "has timeseal; checking")) {
3754                 /* We must have called his flag a little too soon */
3755                 whiteFlag = blackFlag = FALSE;
3756                 continue;
3757             }
3758
3759             if (looking_at(buf, &i, "added * seconds to") ||
3760                 looking_at(buf, &i, "seconds were added to")) {
3761                 /* Update the clocks */
3762                 SendToICS(ics_prefix);
3763                 SendToICS("refresh\n");
3764                 continue;
3765             }
3766
3767             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3768                 ics_clock_paused = TRUE;
3769                 StopClocks();
3770                 continue;
3771             }
3772
3773             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3774                 ics_clock_paused = FALSE;
3775                 StartClocks();
3776                 continue;
3777             }
3778
3779             /* Grab player ratings from the Creating: message.
3780                Note we have to check for the special case when
3781                the ICS inserts things like [white] or [black]. */
3782             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3783                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3784                 /* star_matches:
3785                    0    player 1 name (not necessarily white)
3786                    1    player 1 rating
3787                    2    empty, white, or black (IGNORED)
3788                    3    player 2 name (not necessarily black)
3789                    4    player 2 rating
3790
3791                    The names/ratings are sorted out when the game
3792                    actually starts (below).
3793                 */
3794                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3795                 player1Rating = string_to_rating(star_match[1]);
3796                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3797                 player2Rating = string_to_rating(star_match[4]);
3798
3799                 if (appData.debugMode)
3800                   fprintf(debugFP,
3801                           "Ratings from 'Creating:' %s %d, %s %d\n",
3802                           player1Name, player1Rating,
3803                           player2Name, player2Rating);
3804
3805                 continue;
3806             }
3807
3808             /* Improved generic start/end-of-game messages */
3809             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3810                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3811                 /* If tkind == 0: */
3812                 /* star_match[0] is the game number */
3813                 /*           [1] is the white player's name */
3814                 /*           [2] is the black player's name */
3815                 /* For end-of-game: */
3816                 /*           [3] is the reason for the game end */
3817                 /*           [4] is a PGN end game-token, preceded by " " */
3818                 /* For start-of-game: */
3819                 /*           [3] begins with "Creating" or "Continuing" */
3820                 /*           [4] is " *" or empty (don't care). */
3821                 int gamenum = atoi(star_match[0]);
3822                 char *whitename, *blackname, *why, *endtoken;
3823                 ChessMove endtype = EndOfFile;
3824
3825                 if (tkind == 0) {
3826                   whitename = star_match[1];
3827                   blackname = star_match[2];
3828                   why = star_match[3];
3829                   endtoken = star_match[4];
3830                 } else {
3831                   whitename = star_match[1];
3832                   blackname = star_match[3];
3833                   why = star_match[5];
3834                   endtoken = star_match[6];
3835                 }
3836
3837                 /* Game start messages */
3838                 if (strncmp(why, "Creating ", 9) == 0 ||
3839                     strncmp(why, "Continuing ", 11) == 0) {
3840                     gs_gamenum = gamenum;
3841                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3842                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3843 #if ZIPPY
3844                     if (appData.zippyPlay) {
3845                         ZippyGameStart(whitename, blackname);
3846                     }
3847 #endif /*ZIPPY*/
3848                     partnerBoardValid = FALSE; // [HGM] bughouse
3849                     continue;
3850                 }
3851
3852                 /* Game end messages */
3853                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3854                     ics_gamenum != gamenum) {
3855                     continue;
3856                 }
3857                 while (endtoken[0] == ' ') endtoken++;
3858                 switch (endtoken[0]) {
3859                   case '*':
3860                   default:
3861                     endtype = GameUnfinished;
3862                     break;
3863                   case '0':
3864                     endtype = BlackWins;
3865                     break;
3866                   case '1':
3867                     if (endtoken[1] == '/')
3868                       endtype = GameIsDrawn;
3869                     else
3870                       endtype = WhiteWins;
3871                     break;
3872                 }
3873                 GameEnds(endtype, why, GE_ICS);
3874 #if ZIPPY
3875                 if (appData.zippyPlay && first.initDone) {
3876                     ZippyGameEnd(endtype, why);
3877                     if (first.pr == NULL) {
3878                       /* Start the next process early so that we'll
3879                          be ready for the next challenge */
3880                       StartChessProgram(&first);
3881                     }
3882                     /* Send "new" early, in case this command takes
3883                        a long time to finish, so that we'll be ready
3884                        for the next challenge. */
3885                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3886                     Reset(TRUE, TRUE);
3887                 }
3888 #endif /*ZIPPY*/
3889                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3890                 continue;
3891             }
3892
3893             if (looking_at(buf, &i, "Removing game * from observation") ||
3894                 looking_at(buf, &i, "no longer observing game *") ||
3895                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3896                 if (gameMode == IcsObserving &&
3897                     atoi(star_match[0]) == ics_gamenum)
3898                   {
3899                       /* icsEngineAnalyze */
3900                       if (appData.icsEngineAnalyze) {
3901                             ExitAnalyzeMode();
3902                             ModeHighlight();
3903                       }
3904                       StopClocks();
3905                       gameMode = IcsIdle;
3906                       ics_gamenum = -1;
3907                       ics_user_moved = FALSE;
3908                   }
3909                 continue;
3910             }
3911
3912             if (looking_at(buf, &i, "no longer examining game *")) {
3913                 if (gameMode == IcsExamining &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       gameMode = IcsIdle;
3917                       ics_gamenum = -1;
3918                       ics_user_moved = FALSE;
3919                   }
3920                 continue;
3921             }
3922
3923             /* Advance leftover_start past any newlines we find,
3924                so only partial lines can get reparsed */
3925             if (looking_at(buf, &i, "\n")) {
3926                 prevColor = curColor;
3927                 if (curColor != ColorNormal) {
3928                     if (oldi > next_out) {
3929                         SendToPlayer(&buf[next_out], oldi - next_out);
3930                         next_out = oldi;
3931                     }
3932                     Colorize(ColorNormal, FALSE);
3933                     curColor = ColorNormal;
3934                 }
3935                 if (started == STARTED_BOARD) {
3936                     started = STARTED_NONE;
3937                     parse[parse_pos] = NULLCHAR;
3938                     ParseBoard12(parse);
3939                     ics_user_moved = 0;
3940
3941                     /* Send premove here */
3942                     if (appData.premove) {
3943                       char str[MSG_SIZ];
3944                       if (currentMove == 0 &&
3945                           gameMode == IcsPlayingWhite &&
3946                           appData.premoveWhite) {
3947                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3948                         if (appData.debugMode)
3949                           fprintf(debugFP, "Sending premove:\n");
3950                         SendToICS(str);
3951                       } else if (currentMove == 1 &&
3952                                  gameMode == IcsPlayingBlack &&
3953                                  appData.premoveBlack) {
3954                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3955                         if (appData.debugMode)
3956                           fprintf(debugFP, "Sending premove:\n");
3957                         SendToICS(str);
3958                       } else if (gotPremove) {
3959                         gotPremove = 0;
3960                         ClearPremoveHighlights();
3961                         if (appData.debugMode)
3962                           fprintf(debugFP, "Sending premove:\n");
3963                           UserMoveEvent(premoveFromX, premoveFromY,
3964                                         premoveToX, premoveToY,
3965                                         premovePromoChar);
3966                       }
3967                     }
3968
3969                     /* Usually suppress following prompt */
3970                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3971                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3972                         if (looking_at(buf, &i, "*% ")) {
3973                             savingComment = FALSE;
3974                             suppressKibitz = 0;
3975                         }
3976                     }
3977                     next_out = i;
3978                 } else if (started == STARTED_HOLDINGS) {
3979                     int gamenum;
3980                     char new_piece[MSG_SIZ];
3981                     started = STARTED_NONE;
3982                     parse[parse_pos] = NULLCHAR;
3983                     if (appData.debugMode)
3984                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3985                                                         parse, currentMove);
3986                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3987                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3988                         if (gameInfo.variant == VariantNormal) {
3989                           /* [HGM] We seem to switch variant during a game!
3990                            * Presumably no holdings were displayed, so we have
3991                            * to move the position two files to the right to
3992                            * create room for them!
3993                            */
3994                           VariantClass newVariant;
3995                           switch(gameInfo.boardWidth) { // base guess on board width
3996                                 case 9:  newVariant = VariantShogi; break;
3997                                 case 10: newVariant = VariantGreat; break;
3998                                 default: newVariant = VariantCrazyhouse; break;
3999                           }
4000                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4001                           /* Get a move list just to see the header, which
4002                              will tell us whether this is really bug or zh */
4003                           if (ics_getting_history == H_FALSE) {
4004                             ics_getting_history = H_REQUESTED;
4005                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4006                             SendToICS(str);
4007                           }
4008                         }
4009                         new_piece[0] = NULLCHAR;
4010                         sscanf(parse, "game %d white [%s black [%s <- %s",
4011                                &gamenum, white_holding, black_holding,
4012                                new_piece);
4013                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4014                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4015                         /* [HGM] copy holdings to board holdings area */
4016                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4017                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4018                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4019 #if ZIPPY
4020                         if (appData.zippyPlay && first.initDone) {
4021                             ZippyHoldings(white_holding, black_holding,
4022                                           new_piece);
4023                         }
4024 #endif /*ZIPPY*/
4025                         if (tinyLayout || smallLayout) {
4026                             char wh[16], bh[16];
4027                             PackHolding(wh, white_holding);
4028                             PackHolding(bh, black_holding);
4029                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4030                                     gameInfo.white, gameInfo.black);
4031                         } else {
4032                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4033                                     gameInfo.white, white_holding,
4034                                     gameInfo.black, black_holding);
4035                         }
4036                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4037                         DrawPosition(FALSE, boards[currentMove]);
4038                         DisplayTitle(str);
4039                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4040                         sscanf(parse, "game %d white [%s black [%s <- %s",
4041                                &gamenum, white_holding, black_holding,
4042                                new_piece);
4043                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4044                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4045                         /* [HGM] copy holdings to partner-board holdings area */
4046                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4047                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4048                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4049                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4050                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4051                       }
4052                     }
4053                     /* Suppress following prompt */
4054                     if (looking_at(buf, &i, "*% ")) {
4055                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4056                         savingComment = FALSE;
4057                         suppressKibitz = 0;
4058                     }
4059                     next_out = i;
4060                 }
4061                 continue;
4062             }
4063
4064             i++;                /* skip unparsed character and loop back */
4065         }
4066
4067         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4068 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4069 //          SendToPlayer(&buf[next_out], i - next_out);
4070             started != STARTED_HOLDINGS && leftover_start > next_out) {
4071             SendToPlayer(&buf[next_out], leftover_start - next_out);
4072             next_out = i;
4073         }
4074
4075         leftover_len = buf_len - leftover_start;
4076         /* if buffer ends with something we couldn't parse,
4077            reparse it after appending the next read */
4078
4079     } else if (count == 0) {
4080         RemoveInputSource(isr);
4081         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4082     } else {
4083         DisplayFatalError(_("Error reading from ICS"), error, 1);
4084     }
4085 }
4086
4087
4088 /* Board style 12 looks like this:
4089
4090    <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
4091
4092  * The "<12> " is stripped before it gets to this routine.  The two
4093  * trailing 0's (flip state and clock ticking) are later addition, and
4094  * some chess servers may not have them, or may have only the first.
4095  * Additional trailing fields may be added in the future.
4096  */
4097
4098 #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"
4099
4100 #define RELATION_OBSERVING_PLAYED    0
4101 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4102 #define RELATION_PLAYING_MYMOVE      1
4103 #define RELATION_PLAYING_NOTMYMOVE  -1
4104 #define RELATION_EXAMINING           2
4105 #define RELATION_ISOLATED_BOARD     -3
4106 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4107
4108 void
4109 ParseBoard12(string)
4110      char *string;
4111 {
4112     GameMode newGameMode;
4113     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4114     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4115     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4116     char to_play, board_chars[200];
4117     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4118     char black[32], white[32];
4119     Board board;
4120     int prevMove = currentMove;
4121     int ticking = 2;
4122     ChessMove moveType;
4123     int fromX, fromY, toX, toY;
4124     char promoChar;
4125     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4126     char *bookHit = NULL; // [HGM] book
4127     Boolean weird = FALSE, reqFlag = FALSE;
4128
4129     fromX = fromY = toX = toY = -1;
4130
4131     newGame = FALSE;
4132
4133     if (appData.debugMode)
4134       fprintf(debugFP, _("Parsing board: %s\n"), string);
4135
4136     move_str[0] = NULLCHAR;
4137     elapsed_time[0] = NULLCHAR;
4138     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4139         int  i = 0, j;
4140         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4141             if(string[i] == ' ') { ranks++; files = 0; }
4142             else files++;
4143             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4144             i++;
4145         }
4146         for(j = 0; j <i; j++) board_chars[j] = string[j];
4147         board_chars[i] = '\0';
4148         string += i + 1;
4149     }
4150     n = sscanf(string, PATTERN, &to_play, &double_push,
4151                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4152                &gamenum, white, black, &relation, &basetime, &increment,
4153                &white_stren, &black_stren, &white_time, &black_time,
4154                &moveNum, str, elapsed_time, move_str, &ics_flip,
4155                &ticking);
4156
4157     if (n < 21) {
4158         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4159         DisplayError(str, 0);
4160         return;
4161     }
4162
4163     /* Convert the move number to internal form */
4164     moveNum = (moveNum - 1) * 2;
4165     if (to_play == 'B') moveNum++;
4166     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4167       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4168                         0, 1);
4169       return;
4170     }
4171
4172     switch (relation) {
4173       case RELATION_OBSERVING_PLAYED:
4174       case RELATION_OBSERVING_STATIC:
4175         if (gamenum == -1) {
4176             /* Old ICC buglet */
4177             relation = RELATION_OBSERVING_STATIC;
4178         }
4179         newGameMode = IcsObserving;
4180         break;
4181       case RELATION_PLAYING_MYMOVE:
4182       case RELATION_PLAYING_NOTMYMOVE:
4183         newGameMode =
4184           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4185             IcsPlayingWhite : IcsPlayingBlack;
4186         break;
4187       case RELATION_EXAMINING:
4188         newGameMode = IcsExamining;
4189         break;
4190       case RELATION_ISOLATED_BOARD:
4191       default:
4192         /* Just display this board.  If user was doing something else,
4193            we will forget about it until the next board comes. */
4194         newGameMode = IcsIdle;
4195         break;
4196       case RELATION_STARTING_POSITION:
4197         newGameMode = gameMode;
4198         break;
4199     }
4200
4201     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4202          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4203       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4204       char *toSqr;
4205       for (k = 0; k < ranks; k++) {
4206         for (j = 0; j < files; j++)
4207           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4208         if(gameInfo.holdingsWidth > 1) {
4209              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4210              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4211         }
4212       }
4213       CopyBoard(partnerBoard, board);
4214       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4215         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4216         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4217       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4218       if(toSqr = strchr(str, '-')) {
4219         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4220         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4221       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4222       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4223       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4224       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4225       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4226       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4227                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4228       DisplayMessage(partnerStatus, "");
4229         partnerBoardValid = TRUE;
4230       return;
4231     }
4232
4233     /* Modify behavior for initial board display on move listing
4234        of wild games.
4235        */
4236     switch (ics_getting_history) {
4237       case H_FALSE:
4238       case H_REQUESTED:
4239         break;
4240       case H_GOT_REQ_HEADER:
4241       case H_GOT_UNREQ_HEADER:
4242         /* This is the initial position of the current game */
4243         gamenum = ics_gamenum;
4244         moveNum = 0;            /* old ICS bug workaround */
4245         if (to_play == 'B') {
4246           startedFromSetupPosition = TRUE;
4247           blackPlaysFirst = TRUE;
4248           moveNum = 1;
4249           if (forwardMostMove == 0) forwardMostMove = 1;
4250           if (backwardMostMove == 0) backwardMostMove = 1;
4251           if (currentMove == 0) currentMove = 1;
4252         }
4253         newGameMode = gameMode;
4254         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4255         break;
4256       case H_GOT_UNWANTED_HEADER:
4257         /* This is an initial board that we don't want */
4258         return;
4259       case H_GETTING_MOVES:
4260         /* Should not happen */
4261         DisplayError(_("Error gathering move list: extra board"), 0);
4262         ics_getting_history = H_FALSE;
4263         return;
4264     }
4265
4266    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4267                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4268      /* [HGM] We seem to have switched variant unexpectedly
4269       * Try to guess new variant from board size
4270       */
4271           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4272           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4273           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4274           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4275           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4276           if(!weird) newVariant = VariantNormal;
4277           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4278           /* Get a move list just to see the header, which
4279              will tell us whether this is really bug or zh */
4280           if (ics_getting_history == H_FALSE) {
4281             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4282             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4283             SendToICS(str);
4284           }
4285     }
4286
4287     /* Take action if this is the first board of a new game, or of a
4288        different game than is currently being displayed.  */
4289     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4290         relation == RELATION_ISOLATED_BOARD) {
4291
4292         /* Forget the old game and get the history (if any) of the new one */
4293         if (gameMode != BeginningOfGame) {
4294           Reset(TRUE, TRUE);
4295         }
4296         newGame = TRUE;
4297         if (appData.autoRaiseBoard) BoardToTop();
4298         prevMove = -3;
4299         if (gamenum == -1) {
4300             newGameMode = IcsIdle;
4301         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4302                    appData.getMoveList && !reqFlag) {
4303             /* Need to get game history */
4304             ics_getting_history = H_REQUESTED;
4305             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4306             SendToICS(str);
4307         }
4308
4309         /* Initially flip the board to have black on the bottom if playing
4310            black or if the ICS flip flag is set, but let the user change
4311            it with the Flip View button. */
4312         flipView = appData.autoFlipView ?
4313           (newGameMode == IcsPlayingBlack) || ics_flip :
4314           appData.flipView;
4315
4316         /* Done with values from previous mode; copy in new ones */
4317         gameMode = newGameMode;
4318         ModeHighlight();
4319         ics_gamenum = gamenum;
4320         if (gamenum == gs_gamenum) {
4321             int klen = strlen(gs_kind);
4322             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4323             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4324             gameInfo.event = StrSave(str);
4325         } else {
4326             gameInfo.event = StrSave("ICS game");
4327         }
4328         gameInfo.site = StrSave(appData.icsHost);
4329         gameInfo.date = PGNDate();
4330         gameInfo.round = StrSave("-");
4331         gameInfo.white = StrSave(white);
4332         gameInfo.black = StrSave(black);
4333         timeControl = basetime * 60 * 1000;
4334         timeControl_2 = 0;
4335         timeIncrement = increment * 1000;
4336         movesPerSession = 0;
4337         gameInfo.timeControl = TimeControlTagValue();
4338         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4339   if (appData.debugMode) {
4340     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4341     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4342     setbuf(debugFP, NULL);
4343   }
4344
4345         gameInfo.outOfBook = NULL;
4346
4347         /* Do we have the ratings? */
4348         if (strcmp(player1Name, white) == 0 &&
4349             strcmp(player2Name, black) == 0) {
4350             if (appData.debugMode)
4351               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4352                       player1Rating, player2Rating);
4353             gameInfo.whiteRating = player1Rating;
4354             gameInfo.blackRating = player2Rating;
4355         } else if (strcmp(player2Name, white) == 0 &&
4356                    strcmp(player1Name, black) == 0) {
4357             if (appData.debugMode)
4358               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4359                       player2Rating, player1Rating);
4360             gameInfo.whiteRating = player2Rating;
4361             gameInfo.blackRating = player1Rating;
4362         }
4363         player1Name[0] = player2Name[0] = NULLCHAR;
4364
4365         /* Silence shouts if requested */
4366         if (appData.quietPlay &&
4367             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4368             SendToICS(ics_prefix);
4369             SendToICS("set shout 0\n");
4370         }
4371     }
4372
4373     /* Deal with midgame name changes */
4374     if (!newGame) {
4375         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4376             if (gameInfo.white) free(gameInfo.white);
4377             gameInfo.white = StrSave(white);
4378         }
4379         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4380             if (gameInfo.black) free(gameInfo.black);
4381             gameInfo.black = StrSave(black);
4382         }
4383     }
4384
4385     /* Throw away game result if anything actually changes in examine mode */
4386     if (gameMode == IcsExamining && !newGame) {
4387         gameInfo.result = GameUnfinished;
4388         if (gameInfo.resultDetails != NULL) {
4389             free(gameInfo.resultDetails);
4390             gameInfo.resultDetails = NULL;
4391         }
4392     }
4393
4394     /* In pausing && IcsExamining mode, we ignore boards coming
4395        in if they are in a different variation than we are. */
4396     if (pauseExamInvalid) return;
4397     if (pausing && gameMode == IcsExamining) {
4398         if (moveNum <= pauseExamForwardMostMove) {
4399             pauseExamInvalid = TRUE;
4400             forwardMostMove = pauseExamForwardMostMove;
4401             return;
4402         }
4403     }
4404
4405   if (appData.debugMode) {
4406     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4407   }
4408     /* Parse the board */
4409     for (k = 0; k < ranks; k++) {
4410       for (j = 0; j < files; j++)
4411         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4412       if(gameInfo.holdingsWidth > 1) {
4413            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4414            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4415       }
4416     }
4417     CopyBoard(boards[moveNum], board);
4418     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4419     if (moveNum == 0) {
4420         startedFromSetupPosition =
4421           !CompareBoards(board, initialPosition);
4422         if(startedFromSetupPosition)
4423             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4424     }
4425
4426     /* [HGM] Set castling rights. Take the outermost Rooks,
4427        to make it also work for FRC opening positions. Note that board12
4428        is really defective for later FRC positions, as it has no way to
4429        indicate which Rook can castle if they are on the same side of King.
4430        For the initial position we grant rights to the outermost Rooks,
4431        and remember thos rights, and we then copy them on positions
4432        later in an FRC game. This means WB might not recognize castlings with
4433        Rooks that have moved back to their original position as illegal,
4434        but in ICS mode that is not its job anyway.
4435     */
4436     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4437     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4438
4439         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4440             if(board[0][i] == WhiteRook) j = i;
4441         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4442         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4443             if(board[0][i] == WhiteRook) j = i;
4444         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4446             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4447         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4449             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4450         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4451
4452         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4453         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4454             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4455         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4456             if(board[BOARD_HEIGHT-1][k] == bKing)
4457                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4458         if(gameInfo.variant == VariantTwoKings) {
4459             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4460             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4461             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4462         }
4463     } else { int r;
4464         r = boards[moveNum][CASTLING][0] = initialRights[0];
4465         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4466         r = boards[moveNum][CASTLING][1] = initialRights[1];
4467         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4468         r = boards[moveNum][CASTLING][3] = initialRights[3];
4469         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4470         r = boards[moveNum][CASTLING][4] = initialRights[4];
4471         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4472         /* wildcastle kludge: always assume King has rights */
4473         r = boards[moveNum][CASTLING][2] = initialRights[2];
4474         r = boards[moveNum][CASTLING][5] = initialRights[5];
4475     }
4476     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4477     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4478
4479
4480     if (ics_getting_history == H_GOT_REQ_HEADER ||
4481         ics_getting_history == H_GOT_UNREQ_HEADER) {
4482         /* This was an initial position from a move list, not
4483            the current position */
4484         return;
4485     }
4486
4487     /* Update currentMove and known move number limits */
4488     newMove = newGame || moveNum > forwardMostMove;
4489
4490     if (newGame) {
4491         forwardMostMove = backwardMostMove = currentMove = moveNum;
4492         if (gameMode == IcsExamining && moveNum == 0) {
4493           /* Workaround for ICS limitation: we are not told the wild
4494              type when starting to examine a game.  But if we ask for
4495              the move list, the move list header will tell us */
4496             ics_getting_history = H_REQUESTED;
4497             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4498             SendToICS(str);
4499         }
4500     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4501                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4502 #if ZIPPY
4503         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4504         /* [HGM] applied this also to an engine that is silently watching        */
4505         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4506             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4507             gameInfo.variant == currentlyInitializedVariant) {
4508           takeback = forwardMostMove - moveNum;
4509           for (i = 0; i < takeback; i++) {
4510             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4511             SendToProgram("undo\n", &first);
4512           }
4513         }
4514 #endif
4515
4516         forwardMostMove = moveNum;
4517         if (!pausing || currentMove > forwardMostMove)
4518           currentMove = forwardMostMove;
4519     } else {
4520         /* New part of history that is not contiguous with old part */
4521         if (pausing && gameMode == IcsExamining) {
4522             pauseExamInvalid = TRUE;
4523             forwardMostMove = pauseExamForwardMostMove;
4524             return;
4525         }
4526         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4527 #if ZIPPY
4528             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4529                 // [HGM] when we will receive the move list we now request, it will be
4530                 // fed to the engine from the first move on. So if the engine is not
4531                 // in the initial position now, bring it there.
4532                 InitChessProgram(&first, 0);
4533             }
4534 #endif
4535             ics_getting_history = H_REQUESTED;
4536             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4537             SendToICS(str);
4538         }
4539         forwardMostMove = backwardMostMove = currentMove = moveNum;
4540     }
4541
4542     /* Update the clocks */
4543     if (strchr(elapsed_time, '.')) {
4544       /* Time is in ms */
4545       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4546       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4547     } else {
4548       /* Time is in seconds */
4549       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4550       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4551     }
4552
4553
4554 #if ZIPPY
4555     if (appData.zippyPlay && newGame &&
4556         gameMode != IcsObserving && gameMode != IcsIdle &&
4557         gameMode != IcsExamining)
4558       ZippyFirstBoard(moveNum, basetime, increment);
4559 #endif
4560
4561     /* Put the move on the move list, first converting
4562        to canonical algebraic form. */
4563     if (moveNum > 0) {
4564   if (appData.debugMode) {
4565     if (appData.debugMode) { int f = forwardMostMove;
4566         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4567                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4568                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4569     }
4570     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4571     fprintf(debugFP, "moveNum = %d\n", moveNum);
4572     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4573     setbuf(debugFP, NULL);
4574   }
4575         if (moveNum <= backwardMostMove) {
4576             /* We don't know what the board looked like before
4577                this move.  Punt. */
4578           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4579             strcat(parseList[moveNum - 1], " ");
4580             strcat(parseList[moveNum - 1], elapsed_time);
4581             moveList[moveNum - 1][0] = NULLCHAR;
4582         } else if (strcmp(move_str, "none") == 0) {
4583             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4584             /* Again, we don't know what the board looked like;
4585                this is really the start of the game. */
4586             parseList[moveNum - 1][0] = NULLCHAR;
4587             moveList[moveNum - 1][0] = NULLCHAR;
4588             backwardMostMove = moveNum;
4589             startedFromSetupPosition = TRUE;
4590             fromX = fromY = toX = toY = -1;
4591         } else {
4592           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4593           //                 So we parse the long-algebraic move string in stead of the SAN move
4594           int valid; char buf[MSG_SIZ], *prom;
4595
4596           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4597                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4598           // str looks something like "Q/a1-a2"; kill the slash
4599           if(str[1] == '/')
4600             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4601           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4602           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4603                 strcat(buf, prom); // long move lacks promo specification!
4604           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4605                 if(appData.debugMode)
4606                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4607                 safeStrCpy(move_str, buf, MSG_SIZ);
4608           }
4609           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4610                                 &fromX, &fromY, &toX, &toY, &promoChar)
4611                || ParseOneMove(buf, moveNum - 1, &moveType,
4612                                 &fromX, &fromY, &toX, &toY, &promoChar);
4613           // end of long SAN patch
4614           if (valid) {
4615             (void) CoordsToAlgebraic(boards[moveNum - 1],
4616                                      PosFlags(moveNum - 1),
4617                                      fromY, fromX, toY, toX, promoChar,
4618                                      parseList[moveNum-1]);
4619             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4620               case MT_NONE:
4621               case MT_STALEMATE:
4622               default:
4623                 break;
4624               case MT_CHECK:
4625                 if(gameInfo.variant != VariantShogi)
4626                     strcat(parseList[moveNum - 1], "+");
4627                 break;
4628               case MT_CHECKMATE:
4629               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4630                 strcat(parseList[moveNum - 1], "#");
4631                 break;
4632             }
4633             strcat(parseList[moveNum - 1], " ");
4634             strcat(parseList[moveNum - 1], elapsed_time);
4635             /* currentMoveString is set as a side-effect of ParseOneMove */
4636             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4637             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4638             strcat(moveList[moveNum - 1], "\n");
4639
4640             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4641                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4642               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4643                 ChessSquare old, new = boards[moveNum][k][j];
4644                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4645                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4646                   if(old == new) continue;
4647                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4648                   else if(new == WhiteWazir || new == BlackWazir) {
4649                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4650                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4651                       else boards[moveNum][k][j] = old; // preserve type of Gold
4652                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4653                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4654               }
4655           } else {
4656             /* Move from ICS was illegal!?  Punt. */
4657             if (appData.debugMode) {
4658               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4659               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4660             }
4661             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4662             strcat(parseList[moveNum - 1], " ");
4663             strcat(parseList[moveNum - 1], elapsed_time);
4664             moveList[moveNum - 1][0] = NULLCHAR;
4665             fromX = fromY = toX = toY = -1;
4666           }
4667         }
4668   if (appData.debugMode) {
4669     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4670     setbuf(debugFP, NULL);
4671   }
4672
4673 #if ZIPPY
4674         /* Send move to chess program (BEFORE animating it). */
4675         if (appData.zippyPlay && !newGame && newMove &&
4676            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4677
4678             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4679                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4680                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4681                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4682                             move_str);
4683                     DisplayError(str, 0);
4684                 } else {
4685                     if (first.sendTime) {
4686                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4687                     }
4688                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4689                     if (firstMove && !bookHit) {
4690                         firstMove = FALSE;
4691                         if (first.useColors) {
4692                           SendToProgram(gameMode == IcsPlayingWhite ?
4693                                         "white\ngo\n" :
4694                                         "black\ngo\n", &first);
4695                         } else {
4696                           SendToProgram("go\n", &first);
4697                         }
4698                         first.maybeThinking = TRUE;
4699                     }
4700                 }
4701             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4702               if (moveList[moveNum - 1][0] == NULLCHAR) {
4703                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4704                 DisplayError(str, 0);
4705               } else {
4706                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4707                 SendMoveToProgram(moveNum - 1, &first);
4708               }
4709             }
4710         }
4711 #endif
4712     }
4713
4714     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4715         /* If move comes from a remote source, animate it.  If it
4716            isn't remote, it will have already been animated. */
4717         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4718             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4719         }
4720         if (!pausing && appData.highlightLastMove) {
4721             SetHighlights(fromX, fromY, toX, toY);
4722         }
4723     }
4724
4725     /* Start the clocks */
4726     whiteFlag = blackFlag = FALSE;
4727     appData.clockMode = !(basetime == 0 && increment == 0);
4728     if (ticking == 0) {
4729       ics_clock_paused = TRUE;
4730       StopClocks();
4731     } else if (ticking == 1) {
4732       ics_clock_paused = FALSE;
4733     }
4734     if (gameMode == IcsIdle ||
4735         relation == RELATION_OBSERVING_STATIC ||
4736         relation == RELATION_EXAMINING ||
4737         ics_clock_paused)
4738       DisplayBothClocks();
4739     else
4740       StartClocks();
4741
4742     /* Display opponents and material strengths */
4743     if (gameInfo.variant != VariantBughouse &&
4744         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4745         if (tinyLayout || smallLayout) {
4746             if(gameInfo.variant == VariantNormal)
4747               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4748                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4749                     basetime, increment);
4750             else
4751               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4752                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4753                     basetime, increment, (int) gameInfo.variant);
4754         } else {
4755             if(gameInfo.variant == VariantNormal)
4756               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4757                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4758                     basetime, increment);
4759             else
4760               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4761                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4762                     basetime, increment, VariantName(gameInfo.variant));
4763         }
4764         DisplayTitle(str);
4765   if (appData.debugMode) {
4766     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4767   }
4768     }
4769
4770
4771     /* Display the board */
4772     if (!pausing && !appData.noGUI) {
4773
4774       if (appData.premove)
4775           if (!gotPremove ||
4776              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4777              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4778               ClearPremoveHighlights();
4779
4780       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4781         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4782       DrawPosition(j, boards[currentMove]);
4783
4784       DisplayMove(moveNum - 1);
4785       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4786             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4787               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4788         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4789       }
4790     }
4791
4792     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4793 #if ZIPPY
4794     if(bookHit) { // [HGM] book: simulate book reply
4795         static char bookMove[MSG_SIZ]; // a bit generous?
4796
4797         programStats.nodes = programStats.depth = programStats.time =
4798         programStats.score = programStats.got_only_move = 0;
4799         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4800
4801         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4802         strcat(bookMove, bookHit);
4803         HandleMachineMove(bookMove, &first);
4804     }
4805 #endif
4806 }
4807
4808 void
4809 GetMoveListEvent()
4810 {
4811     char buf[MSG_SIZ];
4812     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4813         ics_getting_history = H_REQUESTED;
4814         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4815         SendToICS(buf);
4816     }
4817 }
4818
4819 void
4820 AnalysisPeriodicEvent(force)
4821      int force;
4822 {
4823     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4824          && !force) || !appData.periodicUpdates)
4825       return;
4826
4827     /* Send . command to Crafty to collect stats */
4828     SendToProgram(".\n", &first);
4829
4830     /* Don't send another until we get a response (this makes
4831        us stop sending to old Crafty's which don't understand
4832        the "." command (sending illegal cmds resets node count & time,
4833        which looks bad)) */
4834     programStats.ok_to_send = 0;
4835 }
4836
4837 void ics_update_width(new_width)
4838         int new_width;
4839 {
4840         ics_printf("set width %d\n", new_width);
4841 }
4842
4843 void
4844 SendMoveToProgram(moveNum, cps)
4845      int moveNum;
4846      ChessProgramState *cps;
4847 {
4848     char buf[MSG_SIZ];
4849
4850     if (cps->useUsermove) {
4851       SendToProgram("usermove ", cps);
4852     }
4853     if (cps->useSAN) {
4854       char *space;
4855       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4856         int len = space - parseList[moveNum];
4857         memcpy(buf, parseList[moveNum], len);
4858         buf[len++] = '\n';
4859         buf[len] = NULLCHAR;
4860       } else {
4861         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4862       }
4863       SendToProgram(buf, cps);
4864     } else {
4865       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4866         AlphaRank(moveList[moveNum], 4);
4867         SendToProgram(moveList[moveNum], cps);
4868         AlphaRank(moveList[moveNum], 4); // and back
4869       } else
4870       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4871        * the engine. It would be nice to have a better way to identify castle
4872        * moves here. */
4873       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4874                                                                          && cps->useOOCastle) {
4875         int fromX = moveList[moveNum][0] - AAA;
4876         int fromY = moveList[moveNum][1] - ONE;
4877         int toX = moveList[moveNum][2] - AAA;
4878         int toY = moveList[moveNum][3] - ONE;
4879         if((boards[moveNum][fromY][fromX] == WhiteKing
4880             && boards[moveNum][toY][toX] == WhiteRook)
4881            || (boards[moveNum][fromY][fromX] == BlackKing
4882                && boards[moveNum][toY][toX] == BlackRook)) {
4883           if(toX > fromX) SendToProgram("O-O\n", cps);
4884           else SendToProgram("O-O-O\n", cps);
4885         }
4886         else SendToProgram(moveList[moveNum], cps);
4887       }
4888       else SendToProgram(moveList[moveNum], cps);
4889       /* End of additions by Tord */
4890     }
4891
4892     /* [HGM] setting up the opening has brought engine in force mode! */
4893     /*       Send 'go' if we are in a mode where machine should play. */
4894     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4895         (gameMode == TwoMachinesPlay   ||
4896 #if ZIPPY
4897          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4898 #endif
4899          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4900         SendToProgram("go\n", cps);
4901   if (appData.debugMode) {
4902     fprintf(debugFP, "(extra)\n");
4903   }
4904     }
4905     setboardSpoiledMachineBlack = 0;
4906 }
4907
4908 void
4909 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4910      ChessMove moveType;
4911      int fromX, fromY, toX, toY;
4912      char promoChar;
4913 {
4914     char user_move[MSG_SIZ];
4915
4916     switch (moveType) {
4917       default:
4918         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4919                 (int)moveType, fromX, fromY, toX, toY);
4920         DisplayError(user_move + strlen("say "), 0);
4921         break;
4922       case WhiteKingSideCastle:
4923       case BlackKingSideCastle:
4924       case WhiteQueenSideCastleWild:
4925       case BlackQueenSideCastleWild:
4926       /* PUSH Fabien */
4927       case WhiteHSideCastleFR:
4928       case BlackHSideCastleFR:
4929       /* POP Fabien */
4930         snprintf(user_move, MSG_SIZ, "o-o\n");
4931         break;
4932       case WhiteQueenSideCastle:
4933       case BlackQueenSideCastle:
4934       case WhiteKingSideCastleWild:
4935       case BlackKingSideCastleWild:
4936       /* PUSH Fabien */
4937       case WhiteASideCastleFR:
4938       case BlackASideCastleFR:
4939       /* POP Fabien */
4940         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4941         break;
4942       case WhiteNonPromotion:
4943       case BlackNonPromotion:
4944         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4945         break;
4946       case WhitePromotion:
4947       case BlackPromotion:
4948         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4949           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4950                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4951                 PieceToChar(WhiteFerz));
4952         else if(gameInfo.variant == VariantGreat)
4953           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4954                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4955                 PieceToChar(WhiteMan));
4956         else
4957           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4958                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4959                 promoChar);
4960         break;
4961       case WhiteDrop:
4962       case BlackDrop:
4963       drop:
4964         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4965                  ToUpper(PieceToChar((ChessSquare) fromX)),
4966                  AAA + toX, ONE + toY);
4967         break;
4968       case IllegalMove:  /* could be a variant we don't quite understand */
4969         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4970       case NormalMove:
4971       case WhiteCapturesEnPassant:
4972       case BlackCapturesEnPassant:
4973         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4974                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4975         break;
4976     }
4977     SendToICS(user_move);
4978     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4979         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4980 }
4981
4982 void
4983 UploadGameEvent()
4984 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4985     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4986     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4987     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4988         DisplayError("You cannot do this while you are playing or observing", 0);
4989         return;
4990     }
4991     if(gameMode != IcsExamining) { // is this ever not the case?
4992         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4993
4994         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4995           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4996         } else { // on FICS we must first go to general examine mode
4997           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4998         }
4999         if(gameInfo.variant != VariantNormal) {
5000             // try figure out wild number, as xboard names are not always valid on ICS
5001             for(i=1; i<=36; i++) {
5002               snprintf(buf, MSG_SIZ, "wild/%d", i);
5003                 if(StringToVariant(buf) == gameInfo.variant) break;
5004             }
5005             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5006             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5007             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5008         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5009         SendToICS(ics_prefix);
5010         SendToICS(buf);
5011         if(startedFromSetupPosition || backwardMostMove != 0) {
5012           fen = PositionToFEN(backwardMostMove, NULL);
5013           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5014             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5015             SendToICS(buf);
5016           } else { // FICS: everything has to set by separate bsetup commands
5017             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5018             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5019             SendToICS(buf);
5020             if(!WhiteOnMove(backwardMostMove)) {
5021                 SendToICS("bsetup tomove black\n");
5022             }
5023             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5024             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5025             SendToICS(buf);
5026             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5027             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5028             SendToICS(buf);
5029             i = boards[backwardMostMove][EP_STATUS];
5030             if(i >= 0) { // set e.p.
5031               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5032                 SendToICS(buf);
5033             }
5034             bsetup++;
5035           }
5036         }
5037       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5038             SendToICS("bsetup done\n"); // switch to normal examining.
5039     }
5040     for(i = backwardMostMove; i<last; i++) {
5041         char buf[20];
5042         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5043         SendToICS(buf);
5044     }
5045     SendToICS(ics_prefix);
5046     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5047 }
5048
5049 void
5050 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5051      int rf, ff, rt, ft;
5052      char promoChar;
5053      char move[7];
5054 {
5055     if (rf == DROP_RANK) {
5056       sprintf(move, "%c@%c%c\n",
5057                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5058     } else {
5059         if (promoChar == 'x' || promoChar == NULLCHAR) {
5060           sprintf(move, "%c%c%c%c\n",
5061                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5062         } else {
5063             sprintf(move, "%c%c%c%c%c\n",
5064                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5065         }
5066     }
5067 }
5068
5069 void
5070 ProcessICSInitScript(f)
5071      FILE *f;
5072 {
5073     char buf[MSG_SIZ];
5074
5075     while (fgets(buf, MSG_SIZ, f)) {
5076         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5077     }
5078
5079     fclose(f);
5080 }
5081
5082
5083 static int lastX, lastY, selectFlag, dragging;
5084
5085 void
5086 Sweep(int step)
5087 {
5088     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5089     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5090     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5091     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5092     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5093     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5094     do {
5095         promoSweep -= step;
5096         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5097         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5098         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5099         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5100         if(!step) step = 1;
5101     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5102             appData.testLegality && (promoSweep == king ||
5103             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5104     ChangeDragPiece(promoSweep);
5105 }
5106
5107 int PromoScroll(int x, int y)
5108 {
5109   int step = 0;
5110
5111   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5112   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5113   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5114   if(!step) return FALSE;
5115   lastX = x; lastY = y;
5116   if((promoSweep < BlackPawn) == flipView) step = -step;
5117   if(step > 0) selectFlag = 1;
5118   if(!selectFlag) Sweep(step);
5119   return FALSE;
5120 }
5121
5122 void
5123 NextPiece(int step)
5124 {
5125     ChessSquare piece = boards[currentMove][toY][toX];
5126     do {
5127         pieceSweep -= step;
5128         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5129         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5130         if(!step) step = -1;
5131     } while(PieceToChar(pieceSweep) == '.');
5132     boards[currentMove][toY][toX] = pieceSweep;
5133     DrawPosition(FALSE, boards[currentMove]);
5134     boards[currentMove][toY][toX] = piece;
5135 }
5136 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5137 void
5138 AlphaRank(char *move, int n)
5139 {
5140 //    char *p = move, c; int x, y;
5141
5142     if (appData.debugMode) {
5143         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5144     }
5145
5146     if(move[1]=='*' &&
5147        move[2]>='0' && move[2]<='9' &&
5148        move[3]>='a' && move[3]<='x'    ) {
5149         move[1] = '@';
5150         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5151         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5152     } else
5153     if(move[0]>='0' && move[0]<='9' &&
5154        move[1]>='a' && move[1]<='x' &&
5155        move[2]>='0' && move[2]<='9' &&
5156        move[3]>='a' && move[3]<='x'    ) {
5157         /* input move, Shogi -> normal */
5158         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5159         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5160         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5161         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5162     } else
5163     if(move[1]=='@' &&
5164        move[3]>='0' && move[3]<='9' &&
5165        move[2]>='a' && move[2]<='x'    ) {
5166         move[1] = '*';
5167         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5168         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5169     } else
5170     if(
5171        move[0]>='a' && move[0]<='x' &&
5172        move[3]>='0' && move[3]<='9' &&
5173        move[2]>='a' && move[2]<='x'    ) {
5174          /* output move, normal -> Shogi */
5175         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5176         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5177         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5178         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5179         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5180     }
5181     if (appData.debugMode) {
5182         fprintf(debugFP, "   out = '%s'\n", move);
5183     }
5184 }
5185
5186 char yy_textstr[8000];
5187
5188 /* Parser for moves from gnuchess, ICS, or user typein box */
5189 Boolean
5190 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5191      char *move;
5192      int moveNum;
5193      ChessMove *moveType;
5194      int *fromX, *fromY, *toX, *toY;
5195      char *promoChar;
5196 {
5197     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5198
5199     switch (*moveType) {
5200       case WhitePromotion:
5201       case BlackPromotion:
5202       case WhiteNonPromotion:
5203       case BlackNonPromotion:
5204       case NormalMove:
5205       case WhiteCapturesEnPassant:
5206       case BlackCapturesEnPassant:
5207       case WhiteKingSideCastle:
5208       case WhiteQueenSideCastle:
5209       case BlackKingSideCastle:
5210       case BlackQueenSideCastle:
5211       case WhiteKingSideCastleWild:
5212       case WhiteQueenSideCastleWild:
5213       case BlackKingSideCastleWild:
5214       case BlackQueenSideCastleWild:
5215       /* Code added by Tord: */
5216       case WhiteHSideCastleFR:
5217       case WhiteASideCastleFR:
5218       case BlackHSideCastleFR:
5219       case BlackASideCastleFR:
5220       /* End of code added by Tord */
5221       case IllegalMove:         /* bug or odd chess variant */
5222         *fromX = currentMoveString[0] - AAA;
5223         *fromY = currentMoveString[1] - ONE;
5224         *toX = currentMoveString[2] - AAA;
5225         *toY = currentMoveString[3] - ONE;
5226         *promoChar = currentMoveString[4];
5227         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5228             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5229     if (appData.debugMode) {
5230         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5231     }
5232             *fromX = *fromY = *toX = *toY = 0;
5233             return FALSE;
5234         }
5235         if (appData.testLegality) {
5236           return (*moveType != IllegalMove);
5237         } else {
5238           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5239                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5240         }
5241
5242       case WhiteDrop:
5243       case BlackDrop:
5244         *fromX = *moveType == WhiteDrop ?
5245           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5246           (int) CharToPiece(ToLower(currentMoveString[0]));
5247         *fromY = DROP_RANK;
5248         *toX = currentMoveString[2] - AAA;
5249         *toY = currentMoveString[3] - ONE;
5250         *promoChar = NULLCHAR;
5251         return TRUE;
5252
5253       case AmbiguousMove:
5254       case ImpossibleMove:
5255       case EndOfFile:
5256       case ElapsedTime:
5257       case Comment:
5258       case PGNTag:
5259       case NAG:
5260       case WhiteWins:
5261       case BlackWins:
5262       case GameIsDrawn:
5263       default:
5264     if (appData.debugMode) {
5265         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5266     }
5267         /* bug? */
5268         *fromX = *fromY = *toX = *toY = 0;
5269         *promoChar = NULLCHAR;
5270         return FALSE;
5271     }
5272 }
5273
5274 Boolean pushed = FALSE;
5275
5276 void
5277 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5278 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5279   int fromX, fromY, toX, toY; char promoChar;
5280   ChessMove moveType;
5281   Boolean valid;
5282   int nr = 0;
5283
5284   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5285     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5286     pushed = TRUE;
5287   }
5288   endPV = forwardMostMove;
5289   do {
5290     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5291     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5292     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5293 if(appData.debugMode){
5294 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);
5295 }
5296     if(!valid && nr == 0 &&
5297        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5298         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5299         // Hande case where played move is different from leading PV move
5300         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5301         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5302         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5303         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5304           endPV += 2; // if position different, keep this
5305           moveList[endPV-1][0] = fromX + AAA;
5306           moveList[endPV-1][1] = fromY + ONE;
5307           moveList[endPV-1][2] = toX + AAA;
5308           moveList[endPV-1][3] = toY + ONE;
5309           parseList[endPV-1][0] = NULLCHAR;
5310           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5311         }
5312       }
5313     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5314     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5315     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5316     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5317         valid++; // allow comments in PV
5318         continue;
5319     }
5320     nr++;
5321     if(endPV+1 > framePtr) break; // no space, truncate
5322     if(!valid) break;
5323     endPV++;
5324     CopyBoard(boards[endPV], boards[endPV-1]);
5325     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5326     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5327     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5328     CoordsToAlgebraic(boards[endPV - 1],
5329                              PosFlags(endPV - 1),
5330                              fromY, fromX, toY, toX, promoChar,
5331                              parseList[endPV - 1]);
5332   } while(valid);
5333   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5334   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5335   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5336                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5337   DrawPosition(TRUE, boards[currentMove]);
5338 }
5339
5340 int
5341 MultiPV(ChessProgramState *cps)
5342 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5343         int i;
5344         for(i=0; i<cps->nrOptions; i++)
5345             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5346                 return i;
5347         return -1;
5348 }
5349
5350 Boolean
5351 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5352 {
5353         int startPV, multi, lineStart, origIndex = index;
5354         char *p, buf2[MSG_SIZ];
5355
5356         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5357         lastX = x; lastY = y;
5358         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5359         lineStart = startPV = index;
5360         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5361         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5362         index = startPV;
5363         do{ while(buf[index] && buf[index] != '\n') index++;
5364         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5365         buf[index] = 0;
5366         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5367                 int n = first.option[multi].value;
5368                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5369                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5370                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5371                 first.option[multi].value = n;
5372                 *start = *end = 0;
5373                 return FALSE;
5374         }
5375         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5376         *start = startPV; *end = index-1;
5377         return TRUE;
5378 }
5379
5380 Boolean
5381 LoadPV(int x, int y)
5382 { // called on right mouse click to load PV
5383   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5384   lastX = x; lastY = y;
5385   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5386   return TRUE;
5387 }
5388
5389 void
5390 UnLoadPV()
5391 {
5392   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5393   if(endPV < 0) return;
5394   endPV = -1;
5395   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5396         Boolean saveAnimate = appData.animate;
5397         if(pushed) {
5398             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5399                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5400             } else storedGames--; // abandon shelved tail of original game
5401         }
5402         pushed = FALSE;
5403         forwardMostMove = currentMove;
5404         currentMove = oldFMM;
5405         appData.animate = FALSE;
5406         ToNrEvent(forwardMostMove);
5407         appData.animate = saveAnimate;
5408   }
5409   currentMove = forwardMostMove;
5410   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5411   ClearPremoveHighlights();
5412   DrawPosition(TRUE, boards[currentMove]);
5413 }
5414
5415 void
5416 MovePV(int x, int y, int h)
5417 { // step through PV based on mouse coordinates (called on mouse move)
5418   int margin = h>>3, step = 0;
5419
5420   // we must somehow check if right button is still down (might be released off board!)
5421   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5422   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5423   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5424   if(!step) return;
5425   lastX = x; lastY = y;
5426
5427   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5428   if(endPV < 0) return;
5429   if(y < margin) step = 1; else
5430   if(y > h - margin) step = -1;
5431   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5432   currentMove += step;
5433   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5434   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5435                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5436   DrawPosition(FALSE, boards[currentMove]);
5437 }
5438
5439
5440 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5441 // All positions will have equal probability, but the current method will not provide a unique
5442 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5443 #define DARK 1
5444 #define LITE 2
5445 #define ANY 3
5446
5447 int squaresLeft[4];
5448 int piecesLeft[(int)BlackPawn];
5449 int seed, nrOfShuffles;
5450
5451 void GetPositionNumber()
5452 {       // sets global variable seed
5453         int i;
5454
5455         seed = appData.defaultFrcPosition;
5456         if(seed < 0) { // randomize based on time for negative FRC position numbers
5457                 for(i=0; i<50; i++) seed += random();
5458                 seed = random() ^ random() >> 8 ^ random() << 8;
5459                 if(seed<0) seed = -seed;
5460         }
5461 }
5462
5463 int put(Board board, int pieceType, int rank, int n, int shade)
5464 // put the piece on the (n-1)-th empty squares of the given shade
5465 {
5466         int i;
5467
5468         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5469                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5470                         board[rank][i] = (ChessSquare) pieceType;
5471                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5472                         squaresLeft[ANY]--;
5473                         piecesLeft[pieceType]--;
5474                         return i;
5475                 }
5476         }
5477         return -1;
5478 }
5479
5480
5481 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5482 // calculate where the next piece goes, (any empty square), and put it there
5483 {
5484         int i;
5485
5486         i = seed % squaresLeft[shade];
5487         nrOfShuffles *= squaresLeft[shade];
5488         seed /= squaresLeft[shade];
5489         put(board, pieceType, rank, i, shade);
5490 }
5491
5492 void AddTwoPieces(Board board, int pieceType, int rank)
5493 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5494 {
5495         int i, n=squaresLeft[ANY], j=n-1, k;
5496
5497         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5498         i = seed % k;  // pick one
5499         nrOfShuffles *= k;
5500         seed /= k;
5501         while(i >= j) i -= j--;
5502         j = n - 1 - j; i += j;
5503         put(board, pieceType, rank, j, ANY);
5504         put(board, pieceType, rank, i, ANY);
5505 }
5506
5507 void SetUpShuffle(Board board, int number)
5508 {
5509         int i, p, first=1;
5510
5511         GetPositionNumber(); nrOfShuffles = 1;
5512
5513         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5514         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5515         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5516
5517         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5518
5519         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5520             p = (int) board[0][i];
5521             if(p < (int) BlackPawn) piecesLeft[p] ++;
5522             board[0][i] = EmptySquare;
5523         }
5524
5525         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5526             // shuffles restricted to allow normal castling put KRR first
5527             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5528                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5529             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5530                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5531             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5532                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5533             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5534                 put(board, WhiteRook, 0, 0, ANY);
5535             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5536         }
5537
5538         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5539             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5540             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5541                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5542                 while(piecesLeft[p] >= 2) {
5543                     AddOnePiece(board, p, 0, LITE);
5544                     AddOnePiece(board, p, 0, DARK);
5545                 }
5546                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5547             }
5548
5549         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5550             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5551             // but we leave King and Rooks for last, to possibly obey FRC restriction
5552             if(p == (int)WhiteRook) continue;
5553             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5554             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5555         }
5556
5557         // now everything is placed, except perhaps King (Unicorn) and Rooks
5558
5559         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5560             // Last King gets castling rights
5561             while(piecesLeft[(int)WhiteUnicorn]) {
5562                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5563                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5564             }
5565
5566             while(piecesLeft[(int)WhiteKing]) {
5567                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5568                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5569             }
5570
5571
5572         } else {
5573             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5574             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5575         }
5576
5577         // Only Rooks can be left; simply place them all
5578         while(piecesLeft[(int)WhiteRook]) {
5579                 i = put(board, WhiteRook, 0, 0, ANY);
5580                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5581                         if(first) {
5582                                 first=0;
5583                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5584                         }
5585                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5586                 }
5587         }
5588         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5589             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5590         }
5591
5592         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5593 }
5594
5595 int SetCharTable( char *table, const char * map )
5596 /* [HGM] moved here from winboard.c because of its general usefulness */
5597 /*       Basically a safe strcpy that uses the last character as King */
5598 {
5599     int result = FALSE; int NrPieces;
5600
5601     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5602                     && NrPieces >= 12 && !(NrPieces&1)) {
5603         int i; /* [HGM] Accept even length from 12 to 34 */
5604
5605         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5606         for( i=0; i<NrPieces/2-1; i++ ) {
5607             table[i] = map[i];
5608             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5609         }
5610         table[(int) WhiteKing]  = map[NrPieces/2-1];
5611         table[(int) BlackKing]  = map[NrPieces-1];
5612
5613         result = TRUE;
5614     }
5615
5616     return result;
5617 }
5618
5619 void Prelude(Board board)
5620 {       // [HGM] superchess: random selection of exo-pieces
5621         int i, j, k; ChessSquare p;
5622         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5623
5624         GetPositionNumber(); // use FRC position number
5625
5626         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5627             SetCharTable(pieceToChar, appData.pieceToCharTable);
5628             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5629                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5630         }
5631
5632         j = seed%4;                 seed /= 4;
5633         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5634         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5635         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5636         j = seed%3 + (seed%3 >= j); seed /= 3;
5637         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5638         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5639         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5640         j = seed%3;                 seed /= 3;
5641         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5642         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5643         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5644         j = seed%2 + (seed%2 >= j); seed /= 2;
5645         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5646         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5647         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5648         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5649         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5650         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5651         put(board, exoPieces[0],    0, 0, ANY);
5652         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5653 }
5654
5655 void
5656 InitPosition(redraw)
5657      int redraw;
5658 {
5659     ChessSquare (* pieces)[BOARD_FILES];
5660     int i, j, pawnRow, overrule,
5661     oldx = gameInfo.boardWidth,
5662     oldy = gameInfo.boardHeight,
5663     oldh = gameInfo.holdingsWidth;
5664     static int oldv;
5665
5666     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5667
5668     /* [AS] Initialize pv info list [HGM] and game status */
5669     {
5670         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5671             pvInfoList[i].depth = 0;
5672             boards[i][EP_STATUS] = EP_NONE;
5673             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5674         }
5675
5676         initialRulePlies = 0; /* 50-move counter start */
5677
5678         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5679         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5680     }
5681
5682
5683     /* [HGM] logic here is completely changed. In stead of full positions */
5684     /* the initialized data only consist of the two backranks. The switch */
5685     /* selects which one we will use, which is than copied to the Board   */
5686     /* initialPosition, which for the rest is initialized by Pawns and    */
5687     /* empty squares. This initial position is then copied to boards[0],  */
5688     /* possibly after shuffling, so that it remains available.            */
5689
5690     gameInfo.holdingsWidth = 0; /* default board sizes */
5691     gameInfo.boardWidth    = 8;
5692     gameInfo.boardHeight   = 8;
5693     gameInfo.holdingsSize  = 0;
5694     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5695     for(i=0; i<BOARD_FILES-2; i++)
5696       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5697     initialPosition[EP_STATUS] = EP_NONE;
5698     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5699     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5700          SetCharTable(pieceNickName, appData.pieceNickNames);
5701     else SetCharTable(pieceNickName, "............");
5702     pieces = FIDEArray;
5703
5704     switch (gameInfo.variant) {
5705     case VariantFischeRandom:
5706       shuffleOpenings = TRUE;
5707     default:
5708       break;
5709     case VariantShatranj:
5710       pieces = ShatranjArray;
5711       nrCastlingRights = 0;
5712       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5713       break;
5714     case VariantMakruk:
5715       pieces = makrukArray;
5716       nrCastlingRights = 0;
5717       startedFromSetupPosition = TRUE;
5718       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5719       break;
5720     case VariantTwoKings:
5721       pieces = twoKingsArray;
5722       break;
5723     case VariantCapaRandom:
5724       shuffleOpenings = TRUE;
5725     case VariantCapablanca:
5726       pieces = CapablancaArray;
5727       gameInfo.boardWidth = 10;
5728       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5729       break;
5730     case VariantGothic:
5731       pieces = GothicArray;
5732       gameInfo.boardWidth = 10;
5733       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5734       break;
5735     case VariantSChess:
5736       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5737       gameInfo.holdingsSize = 7;
5738       break;
5739     case VariantJanus:
5740       pieces = JanusArray;
5741       gameInfo.boardWidth = 10;
5742       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5743       nrCastlingRights = 6;
5744         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5745         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5746         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5747         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5748         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5749         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5750       break;
5751     case VariantFalcon:
5752       pieces = FalconArray;
5753       gameInfo.boardWidth = 10;
5754       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5755       break;
5756     case VariantXiangqi:
5757       pieces = XiangqiArray;
5758       gameInfo.boardWidth  = 9;
5759       gameInfo.boardHeight = 10;
5760       nrCastlingRights = 0;
5761       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5762       break;
5763     case VariantShogi:
5764       pieces = ShogiArray;
5765       gameInfo.boardWidth  = 9;
5766       gameInfo.boardHeight = 9;
5767       gameInfo.holdingsSize = 7;
5768       nrCastlingRights = 0;
5769       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5770       break;
5771     case VariantCourier:
5772       pieces = CourierArray;
5773       gameInfo.boardWidth  = 12;
5774       nrCastlingRights = 0;
5775       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5776       break;
5777     case VariantKnightmate:
5778       pieces = KnightmateArray;
5779       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5780       break;
5781     case VariantSpartan:
5782       pieces = SpartanArray;
5783       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5784       break;
5785     case VariantFairy:
5786       pieces = fairyArray;
5787       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5788       break;
5789     case VariantGreat:
5790       pieces = GreatArray;
5791       gameInfo.boardWidth = 10;
5792       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5793       gameInfo.holdingsSize = 8;
5794       break;
5795     case VariantSuper:
5796       pieces = FIDEArray;
5797       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5798       gameInfo.holdingsSize = 8;
5799       startedFromSetupPosition = TRUE;
5800       break;
5801     case VariantCrazyhouse:
5802     case VariantBughouse:
5803       pieces = FIDEArray;
5804       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5805       gameInfo.holdingsSize = 5;
5806       break;
5807     case VariantWildCastle:
5808       pieces = FIDEArray;
5809       /* !!?shuffle with kings guaranteed to be on d or e file */
5810       shuffleOpenings = 1;
5811       break;
5812     case VariantNoCastle:
5813       pieces = FIDEArray;
5814       nrCastlingRights = 0;
5815       /* !!?unconstrained back-rank shuffle */
5816       shuffleOpenings = 1;
5817       break;
5818     }
5819
5820     overrule = 0;
5821     if(appData.NrFiles >= 0) {
5822         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5823         gameInfo.boardWidth = appData.NrFiles;
5824     }
5825     if(appData.NrRanks >= 0) {
5826         gameInfo.boardHeight = appData.NrRanks;
5827     }
5828     if(appData.holdingsSize >= 0) {
5829         i = appData.holdingsSize;
5830         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5831         gameInfo.holdingsSize = i;
5832     }
5833     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5834     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5835         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5836
5837     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5838     if(pawnRow < 1) pawnRow = 1;
5839     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5840
5841     /* User pieceToChar list overrules defaults */
5842     if(appData.pieceToCharTable != NULL)
5843         SetCharTable(pieceToChar, appData.pieceToCharTable);
5844
5845     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5846
5847         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5848             s = (ChessSquare) 0; /* account holding counts in guard band */
5849         for( i=0; i<BOARD_HEIGHT; i++ )
5850             initialPosition[i][j] = s;
5851
5852         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5853         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5854         initialPosition[pawnRow][j] = WhitePawn;
5855         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5856         if(gameInfo.variant == VariantXiangqi) {
5857             if(j&1) {
5858                 initialPosition[pawnRow][j] =
5859                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5860                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5861                    initialPosition[2][j] = WhiteCannon;
5862                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5863                 }
5864             }
5865         }
5866         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5867     }
5868     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5869
5870             j=BOARD_LEFT+1;
5871             initialPosition[1][j] = WhiteBishop;
5872             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5873             j=BOARD_RGHT-2;
5874             initialPosition[1][j] = WhiteRook;
5875             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5876     }
5877
5878     if( nrCastlingRights == -1) {
5879         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5880         /*       This sets default castling rights from none to normal corners   */
5881         /* Variants with other castling rights must set them themselves above    */
5882         nrCastlingRights = 6;
5883
5884         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5885         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5886         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5887         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5888         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5889         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5890      }
5891
5892      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5893      if(gameInfo.variant == VariantGreat) { // promotion commoners
5894         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5895         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5896         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5897         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5898      }
5899      if( gameInfo.variant == VariantSChess ) {
5900       initialPosition[1][0] = BlackMarshall;
5901       initialPosition[2][0] = BlackAngel;
5902       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5903       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5904       initialPosition[1][1] = initialPosition[2][1] = 
5905       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5906      }
5907   if (appData.debugMode) {
5908     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5909   }
5910     if(shuffleOpenings) {
5911         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5912         startedFromSetupPosition = TRUE;
5913     }
5914     if(startedFromPositionFile) {
5915       /* [HGM] loadPos: use PositionFile for every new game */
5916       CopyBoard(initialPosition, filePosition);
5917       for(i=0; i<nrCastlingRights; i++)
5918           initialRights[i] = filePosition[CASTLING][i];
5919       startedFromSetupPosition = TRUE;
5920     }
5921
5922     CopyBoard(boards[0], initialPosition);
5923
5924     if(oldx != gameInfo.boardWidth ||
5925        oldy != gameInfo.boardHeight ||
5926        oldv != gameInfo.variant ||
5927        oldh != gameInfo.holdingsWidth
5928                                          )
5929             InitDrawingSizes(-2 ,0);
5930
5931     oldv = gameInfo.variant;
5932     if (redraw)
5933       DrawPosition(TRUE, boards[currentMove]);
5934 }
5935
5936 void
5937 SendBoard(cps, moveNum)
5938      ChessProgramState *cps;
5939      int moveNum;
5940 {
5941     char message[MSG_SIZ];
5942
5943     if (cps->useSetboard) {
5944       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5945       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5946       SendToProgram(message, cps);
5947       free(fen);
5948
5949     } else {
5950       ChessSquare *bp;
5951       int i, j;
5952       /* Kludge to set black to move, avoiding the troublesome and now
5953        * deprecated "black" command.
5954        */
5955       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5956         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5957
5958       SendToProgram("edit\n", cps);
5959       SendToProgram("#\n", cps);
5960       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5961         bp = &boards[moveNum][i][BOARD_LEFT];
5962         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5963           if ((int) *bp < (int) BlackPawn) {
5964             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5965                     AAA + j, ONE + i);
5966             if(message[0] == '+' || message[0] == '~') {
5967               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5968                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5969                         AAA + j, ONE + i);
5970             }
5971             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5972                 message[1] = BOARD_RGHT   - 1 - j + '1';
5973                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5974             }
5975             SendToProgram(message, cps);
5976           }
5977         }
5978       }
5979
5980       SendToProgram("c\n", cps);
5981       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5982         bp = &boards[moveNum][i][BOARD_LEFT];
5983         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5984           if (((int) *bp != (int) EmptySquare)
5985               && ((int) *bp >= (int) BlackPawn)) {
5986             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5987                     AAA + j, ONE + i);
5988             if(message[0] == '+' || message[0] == '~') {
5989               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5990                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5991                         AAA + j, ONE + i);
5992             }
5993             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5994                 message[1] = BOARD_RGHT   - 1 - j + '1';
5995                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5996             }
5997             SendToProgram(message, cps);
5998           }
5999         }
6000       }
6001
6002       SendToProgram(".\n", cps);
6003     }
6004     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6005 }
6006
6007 ChessSquare
6008 DefaultPromoChoice(int white)
6009 {
6010     ChessSquare result;
6011     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6012         result = WhiteFerz; // no choice
6013     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6014         result= WhiteKing; // in Suicide Q is the last thing we want
6015     else if(gameInfo.variant == VariantSpartan)
6016         result = white ? WhiteQueen : WhiteAngel;
6017     else result = WhiteQueen;
6018     if(!white) result = WHITE_TO_BLACK result;
6019     return result;
6020 }
6021
6022 static int autoQueen; // [HGM] oneclick
6023
6024 int
6025 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6026 {
6027     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6028     /* [HGM] add Shogi promotions */
6029     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6030     ChessSquare piece;
6031     ChessMove moveType;
6032     Boolean premove;
6033
6034     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6035     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6036
6037     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6038       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6039         return FALSE;
6040
6041     piece = boards[currentMove][fromY][fromX];
6042     if(gameInfo.variant == VariantShogi) {
6043         promotionZoneSize = BOARD_HEIGHT/3;
6044         highestPromotingPiece = (int)WhiteFerz;
6045     } else if(gameInfo.variant == VariantMakruk) {
6046         promotionZoneSize = 3;
6047     }
6048
6049     // Treat Lance as Pawn when it is not representing Amazon
6050     if(gameInfo.variant != VariantSuper) {
6051         if(piece == WhiteLance) piece = WhitePawn; else
6052         if(piece == BlackLance) piece = BlackPawn;
6053     }
6054
6055     // next weed out all moves that do not touch the promotion zone at all
6056     if((int)piece >= BlackPawn) {
6057         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6058              return FALSE;
6059         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6060     } else {
6061         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6062            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6063     }
6064
6065     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6066
6067     // weed out mandatory Shogi promotions
6068     if(gameInfo.variant == VariantShogi) {
6069         if(piece >= BlackPawn) {
6070             if(toY == 0 && piece == BlackPawn ||
6071                toY == 0 && piece == BlackQueen ||
6072                toY <= 1 && piece == BlackKnight) {
6073                 *promoChoice = '+';
6074                 return FALSE;
6075             }
6076         } else {
6077             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6078                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6079                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6080                 *promoChoice = '+';
6081                 return FALSE;
6082             }
6083         }
6084     }
6085
6086     // weed out obviously illegal Pawn moves
6087     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6088         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6089         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6090         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6091         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6092         // note we are not allowed to test for valid (non-)capture, due to premove
6093     }
6094
6095     // we either have a choice what to promote to, or (in Shogi) whether to promote
6096     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6097         *promoChoice = PieceToChar(BlackFerz);  // no choice
6098         return FALSE;
6099     }
6100     // no sense asking what we must promote to if it is going to explode...
6101     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6102         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6103         return FALSE;
6104     }
6105     // give caller the default choice even if we will not make it
6106     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6107     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6108     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6109                            && gameInfo.variant != VariantShogi
6110                            && gameInfo.variant != VariantSuper) return FALSE;
6111     if(autoQueen) return FALSE; // predetermined
6112
6113     // suppress promotion popup on illegal moves that are not premoves
6114     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6115               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6116     if(appData.testLegality && !premove) {
6117         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6118                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6119         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6120             return FALSE;
6121     }
6122
6123     return TRUE;
6124 }
6125
6126 int
6127 InPalace(row, column)
6128      int row, column;
6129 {   /* [HGM] for Xiangqi */
6130     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6131          column < (BOARD_WIDTH + 4)/2 &&
6132          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6133     return FALSE;
6134 }
6135
6136 int
6137 PieceForSquare (x, y)
6138      int x;
6139      int y;
6140 {
6141   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6142      return -1;
6143   else
6144      return boards[currentMove][y][x];
6145 }
6146
6147 int
6148 OKToStartUserMove(x, y)
6149      int x, y;
6150 {
6151     ChessSquare from_piece;
6152     int white_piece;
6153
6154     if (matchMode) return FALSE;
6155     if (gameMode == EditPosition) return TRUE;
6156
6157     if (x >= 0 && y >= 0)
6158       from_piece = boards[currentMove][y][x];
6159     else
6160       from_piece = EmptySquare;
6161
6162     if (from_piece == EmptySquare) return FALSE;
6163
6164     white_piece = (int)from_piece >= (int)WhitePawn &&
6165       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6166
6167     switch (gameMode) {
6168       case PlayFromGameFile:
6169       case AnalyzeFile:
6170       case TwoMachinesPlay:
6171       case EndOfGame:
6172         return FALSE;
6173
6174       case IcsObserving:
6175       case IcsIdle:
6176         return FALSE;
6177
6178       case MachinePlaysWhite:
6179       case IcsPlayingBlack:
6180         if (appData.zippyPlay) return FALSE;
6181         if (white_piece) {
6182             DisplayMoveError(_("You are playing Black"));
6183             return FALSE;
6184         }
6185         break;
6186
6187       case MachinePlaysBlack:
6188       case IcsPlayingWhite:
6189         if (appData.zippyPlay) return FALSE;
6190         if (!white_piece) {
6191             DisplayMoveError(_("You are playing White"));
6192             return FALSE;
6193         }
6194         break;
6195
6196       case EditGame:
6197         if (!white_piece && WhiteOnMove(currentMove)) {
6198             DisplayMoveError(_("It is White's turn"));
6199             return FALSE;
6200         }
6201         if (white_piece && !WhiteOnMove(currentMove)) {
6202             DisplayMoveError(_("It is Black's turn"));
6203             return FALSE;
6204         }
6205         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6206             /* Editing correspondence game history */
6207             /* Could disallow this or prompt for confirmation */
6208             cmailOldMove = -1;
6209         }
6210         break;
6211
6212       case BeginningOfGame:
6213         if (appData.icsActive) return FALSE;
6214         if (!appData.noChessProgram) {
6215             if (!white_piece) {
6216                 DisplayMoveError(_("You are playing White"));
6217                 return FALSE;
6218             }
6219         }
6220         break;
6221
6222       case Training:
6223         if (!white_piece && WhiteOnMove(currentMove)) {
6224             DisplayMoveError(_("It is White's turn"));
6225             return FALSE;
6226         }
6227         if (white_piece && !WhiteOnMove(currentMove)) {
6228             DisplayMoveError(_("It is Black's turn"));
6229             return FALSE;
6230         }
6231         break;
6232
6233       default:
6234       case IcsExamining:
6235         break;
6236     }
6237     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6238         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6239         && gameMode != AnalyzeFile && gameMode != Training) {
6240         DisplayMoveError(_("Displayed position is not current"));
6241         return FALSE;
6242     }
6243     return TRUE;
6244 }
6245
6246 Boolean
6247 OnlyMove(int *x, int *y, Boolean captures) {
6248     DisambiguateClosure cl;
6249     if (appData.zippyPlay) return FALSE;
6250     switch(gameMode) {
6251       case MachinePlaysBlack:
6252       case IcsPlayingWhite:
6253       case BeginningOfGame:
6254         if(!WhiteOnMove(currentMove)) return FALSE;
6255         break;
6256       case MachinePlaysWhite:
6257       case IcsPlayingBlack:
6258         if(WhiteOnMove(currentMove)) return FALSE;
6259         break;
6260       case EditGame:
6261         break;
6262       default:
6263         return FALSE;
6264     }
6265     cl.pieceIn = EmptySquare;
6266     cl.rfIn = *y;
6267     cl.ffIn = *x;
6268     cl.rtIn = -1;
6269     cl.ftIn = -1;
6270     cl.promoCharIn = NULLCHAR;
6271     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6272     if( cl.kind == NormalMove ||
6273         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6274         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6275         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6276       fromX = cl.ff;
6277       fromY = cl.rf;
6278       *x = cl.ft;
6279       *y = cl.rt;
6280       return TRUE;
6281     }
6282     if(cl.kind != ImpossibleMove) return FALSE;
6283     cl.pieceIn = EmptySquare;
6284     cl.rfIn = -1;
6285     cl.ffIn = -1;
6286     cl.rtIn = *y;
6287     cl.ftIn = *x;
6288     cl.promoCharIn = NULLCHAR;
6289     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6290     if( cl.kind == NormalMove ||
6291         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6292         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6293         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6294       fromX = cl.ff;
6295       fromY = cl.rf;
6296       *x = cl.ft;
6297       *y = cl.rt;
6298       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6299       return TRUE;
6300     }
6301     return FALSE;
6302 }
6303
6304 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6305 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6306 int lastLoadGameUseList = FALSE;
6307 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6308 ChessMove lastLoadGameStart = EndOfFile;
6309
6310 void
6311 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6312      int fromX, fromY, toX, toY;
6313      int promoChar;
6314 {
6315     ChessMove moveType;
6316     ChessSquare pdown, pup;
6317
6318     /* Check if the user is playing in turn.  This is complicated because we
6319        let the user "pick up" a piece before it is his turn.  So the piece he
6320        tried to pick up may have been captured by the time he puts it down!
6321        Therefore we use the color the user is supposed to be playing in this
6322        test, not the color of the piece that is currently on the starting
6323        square---except in EditGame mode, where the user is playing both
6324        sides; fortunately there the capture race can't happen.  (It can
6325        now happen in IcsExamining mode, but that's just too bad.  The user
6326        will get a somewhat confusing message in that case.)
6327        */
6328
6329     switch (gameMode) {
6330       case PlayFromGameFile:
6331       case AnalyzeFile:
6332       case TwoMachinesPlay:
6333       case EndOfGame:
6334       case IcsObserving:
6335       case IcsIdle:
6336         /* We switched into a game mode where moves are not accepted,
6337            perhaps while the mouse button was down. */
6338         return;
6339
6340       case MachinePlaysWhite:
6341         /* User is moving for Black */
6342         if (WhiteOnMove(currentMove)) {
6343             DisplayMoveError(_("It is White's turn"));
6344             return;
6345         }
6346         break;
6347
6348       case MachinePlaysBlack:
6349         /* User is moving for White */
6350         if (!WhiteOnMove(currentMove)) {
6351             DisplayMoveError(_("It is Black's turn"));
6352             return;
6353         }
6354         break;
6355
6356       case EditGame:
6357       case IcsExamining:
6358       case BeginningOfGame:
6359       case AnalyzeMode:
6360       case Training:
6361         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6362         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6363             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6364             /* User is moving for Black */
6365             if (WhiteOnMove(currentMove)) {
6366                 DisplayMoveError(_("It is White's turn"));
6367                 return;
6368             }
6369         } else {
6370             /* User is moving for White */
6371             if (!WhiteOnMove(currentMove)) {
6372                 DisplayMoveError(_("It is Black's turn"));
6373                 return;
6374             }
6375         }
6376         break;
6377
6378       case IcsPlayingBlack:
6379         /* User is moving for Black */
6380         if (WhiteOnMove(currentMove)) {
6381             if (!appData.premove) {
6382                 DisplayMoveError(_("It is White's turn"));
6383             } else if (toX >= 0 && toY >= 0) {
6384                 premoveToX = toX;
6385                 premoveToY = toY;
6386                 premoveFromX = fromX;
6387                 premoveFromY = fromY;
6388                 premovePromoChar = promoChar;
6389                 gotPremove = 1;
6390                 if (appData.debugMode)
6391                     fprintf(debugFP, "Got premove: fromX %d,"
6392                             "fromY %d, toX %d, toY %d\n",
6393                             fromX, fromY, toX, toY);
6394             }
6395             return;
6396         }
6397         break;
6398
6399       case IcsPlayingWhite:
6400         /* User is moving for White */
6401         if (!WhiteOnMove(currentMove)) {
6402             if (!appData.premove) {
6403                 DisplayMoveError(_("It is Black's turn"));
6404             } else if (toX >= 0 && toY >= 0) {
6405                 premoveToX = toX;
6406                 premoveToY = toY;
6407                 premoveFromX = fromX;
6408                 premoveFromY = fromY;
6409                 premovePromoChar = promoChar;
6410                 gotPremove = 1;
6411                 if (appData.debugMode)
6412                     fprintf(debugFP, "Got premove: fromX %d,"
6413                             "fromY %d, toX %d, toY %d\n",
6414                             fromX, fromY, toX, toY);
6415             }
6416             return;
6417         }
6418         break;
6419
6420       default:
6421         break;
6422
6423       case EditPosition:
6424         /* EditPosition, empty square, or different color piece;
6425            click-click move is possible */
6426         if (toX == -2 || toY == -2) {
6427             boards[0][fromY][fromX] = EmptySquare;
6428             DrawPosition(FALSE, boards[currentMove]);
6429             return;
6430         } else if (toX >= 0 && toY >= 0) {
6431             boards[0][toY][toX] = boards[0][fromY][fromX];
6432             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6433                 if(boards[0][fromY][0] != EmptySquare) {
6434                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6435                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6436                 }
6437             } else
6438             if(fromX == BOARD_RGHT+1) {
6439                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6440                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6441                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6442                 }
6443             } else
6444             boards[0][fromY][fromX] = EmptySquare;
6445             DrawPosition(FALSE, boards[currentMove]);
6446             return;
6447         }
6448         return;
6449     }
6450
6451     if(toX < 0 || toY < 0) return;
6452     pdown = boards[currentMove][fromY][fromX];
6453     pup = boards[currentMove][toY][toX];
6454
6455     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6456     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6457          if( pup != EmptySquare ) return;
6458          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6459            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6460                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6461            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6462            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6463            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6464            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6465          fromY = DROP_RANK;
6466     }
6467
6468     /* [HGM] always test for legality, to get promotion info */
6469     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6470                                          fromY, fromX, toY, toX, promoChar);
6471     /* [HGM] but possibly ignore an IllegalMove result */
6472     if (appData.testLegality) {
6473         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6474             DisplayMoveError(_("Illegal move"));
6475             return;
6476         }
6477     }
6478
6479     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6480 }
6481
6482 /* Common tail of UserMoveEvent and DropMenuEvent */
6483 int
6484 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6485      ChessMove moveType;
6486      int fromX, fromY, toX, toY;
6487      /*char*/int promoChar;
6488 {
6489     char *bookHit = 0;
6490
6491     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6492         // [HGM] superchess: suppress promotions to non-available piece
6493         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6494         if(WhiteOnMove(currentMove)) {
6495             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6496         } else {
6497             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6498         }
6499     }
6500
6501     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6502        move type in caller when we know the move is a legal promotion */
6503     if(moveType == NormalMove && promoChar)
6504         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6505
6506     /* [HGM] <popupFix> The following if has been moved here from
6507        UserMoveEvent(). Because it seemed to belong here (why not allow
6508        piece drops in training games?), and because it can only be
6509        performed after it is known to what we promote. */
6510     if (gameMode == Training) {
6511       /* compare the move played on the board to the next move in the
6512        * game. If they match, display the move and the opponent's response.
6513        * If they don't match, display an error message.
6514        */
6515       int saveAnimate;
6516       Board testBoard;
6517       CopyBoard(testBoard, boards[currentMove]);
6518       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6519
6520       if (CompareBoards(testBoard, boards[currentMove+1])) {
6521         ForwardInner(currentMove+1);
6522
6523         /* Autoplay the opponent's response.
6524          * if appData.animate was TRUE when Training mode was entered,
6525          * the response will be animated.
6526          */
6527         saveAnimate = appData.animate;
6528         appData.animate = animateTraining;
6529         ForwardInner(currentMove+1);
6530         appData.animate = saveAnimate;
6531
6532         /* check for the end of the game */
6533         if (currentMove >= forwardMostMove) {
6534           gameMode = PlayFromGameFile;
6535           ModeHighlight();
6536           SetTrainingModeOff();
6537           DisplayInformation(_("End of game"));
6538         }
6539       } else {
6540         DisplayError(_("Incorrect move"), 0);
6541       }
6542       return 1;
6543     }
6544
6545   /* Ok, now we know that the move is good, so we can kill
6546      the previous line in Analysis Mode */
6547   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6548                                 && currentMove < forwardMostMove) {
6549     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6550     else forwardMostMove = currentMove;
6551   }
6552
6553   /* If we need the chess program but it's dead, restart it */
6554   ResurrectChessProgram();
6555
6556   /* A user move restarts a paused game*/
6557   if (pausing)
6558     PauseEvent();
6559
6560   thinkOutput[0] = NULLCHAR;
6561
6562   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6563
6564   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6565     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6566     return 1;
6567   }
6568
6569   if (gameMode == BeginningOfGame) {
6570     if (appData.noChessProgram) {
6571       gameMode = EditGame;
6572       SetGameInfo();
6573     } else {
6574       char buf[MSG_SIZ];
6575       gameMode = MachinePlaysBlack;
6576       StartClocks();
6577       SetGameInfo();
6578       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6579       DisplayTitle(buf);
6580       if (first.sendName) {
6581         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6582         SendToProgram(buf, &first);
6583       }
6584       StartClocks();
6585     }
6586     ModeHighlight();
6587   }
6588
6589   /* Relay move to ICS or chess engine */
6590   if (appData.icsActive) {
6591     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6592         gameMode == IcsExamining) {
6593       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6594         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6595         SendToICS("draw ");
6596         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6597       }
6598       // also send plain move, in case ICS does not understand atomic claims
6599       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6600       ics_user_moved = 1;
6601     }
6602   } else {
6603     if (first.sendTime && (gameMode == BeginningOfGame ||
6604                            gameMode == MachinePlaysWhite ||
6605                            gameMode == MachinePlaysBlack)) {
6606       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6607     }
6608     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6609          // [HGM] book: if program might be playing, let it use book
6610         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6611         first.maybeThinking = TRUE;
6612     } else SendMoveToProgram(forwardMostMove-1, &first);
6613     if (currentMove == cmailOldMove + 1) {
6614       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6615     }
6616   }
6617
6618   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6619
6620   switch (gameMode) {
6621   case EditGame:
6622     if(appData.testLegality)
6623     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6624     case MT_NONE:
6625     case MT_CHECK:
6626       break;
6627     case MT_CHECKMATE:
6628     case MT_STAINMATE:
6629       if (WhiteOnMove(currentMove)) {
6630         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6631       } else {
6632         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6633       }
6634       break;
6635     case MT_STALEMATE:
6636       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6637       break;
6638     }
6639     break;
6640
6641   case MachinePlaysBlack:
6642   case MachinePlaysWhite:
6643     /* disable certain menu options while machine is thinking */
6644     SetMachineThinkingEnables();
6645     break;
6646
6647   default:
6648     break;
6649   }
6650
6651   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6652   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6653
6654   if(bookHit) { // [HGM] book: simulate book reply
6655         static char bookMove[MSG_SIZ]; // a bit generous?
6656
6657         programStats.nodes = programStats.depth = programStats.time =
6658         programStats.score = programStats.got_only_move = 0;
6659         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6660
6661         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6662         strcat(bookMove, bookHit);
6663         HandleMachineMove(bookMove, &first);
6664   }
6665   return 1;
6666 }
6667
6668 void
6669 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6670      Board board;
6671      int flags;
6672      ChessMove kind;
6673      int rf, ff, rt, ft;
6674      VOIDSTAR closure;
6675 {
6676     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6677     Markers *m = (Markers *) closure;
6678     if(rf == fromY && ff == fromX)
6679         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6680                          || kind == WhiteCapturesEnPassant
6681                          || kind == BlackCapturesEnPassant);
6682     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6683 }
6684
6685 void
6686 MarkTargetSquares(int clear)
6687 {
6688   int x, y;
6689   if(!appData.markers || !appData.highlightDragging ||
6690      !appData.testLegality || gameMode == EditPosition) return;
6691   if(clear) {
6692     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6693   } else {
6694     int capt = 0;
6695     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6696     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6697       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6698       if(capt)
6699       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6700     }
6701   }
6702   DrawPosition(TRUE, NULL);
6703 }
6704
6705 int
6706 Explode(Board board, int fromX, int fromY, int toX, int toY)
6707 {
6708     if(gameInfo.variant == VariantAtomic &&
6709        (board[toY][toX] != EmptySquare ||                     // capture?
6710         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6711                          board[fromY][fromX] == BlackPawn   )
6712       )) {
6713         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6714         return TRUE;
6715     }
6716     return FALSE;
6717 }
6718
6719 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6720
6721 int CanPromote(ChessSquare piece, int y)
6722 {
6723         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6724         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6725         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6726            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6727            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6728                                                   gameInfo.variant == VariantMakruk) return FALSE;
6729         return (piece == BlackPawn && y == 1 ||
6730                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6731                 piece == BlackLance && y == 1 ||
6732                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6733 }
6734
6735 void LeftClick(ClickType clickType, int xPix, int yPix)
6736 {
6737     int x, y;
6738     Boolean saveAnimate;
6739     static int second = 0, promotionChoice = 0, clearFlag = 0;
6740     char promoChoice = NULLCHAR;
6741     ChessSquare piece;
6742
6743     if(appData.seekGraph && appData.icsActive && loggedOn &&
6744         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6745         SeekGraphClick(clickType, xPix, yPix, 0);
6746         return;
6747     }
6748
6749     if (clickType == Press) ErrorPopDown();
6750     MarkTargetSquares(1);
6751
6752     x = EventToSquare(xPix, BOARD_WIDTH);
6753     y = EventToSquare(yPix, BOARD_HEIGHT);
6754     if (!flipView && y >= 0) {
6755         y = BOARD_HEIGHT - 1 - y;
6756     }
6757     if (flipView && x >= 0) {
6758         x = BOARD_WIDTH - 1 - x;
6759     }
6760
6761     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6762         defaultPromoChoice = promoSweep;
6763         promoSweep = EmptySquare;   // terminate sweep
6764         promoDefaultAltered = TRUE;
6765         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6766     }
6767
6768     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6769         if(clickType == Release) return; // ignore upclick of click-click destination
6770         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6771         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6772         if(gameInfo.holdingsWidth &&
6773                 (WhiteOnMove(currentMove)
6774                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6775                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6776             // click in right holdings, for determining promotion piece
6777             ChessSquare p = boards[currentMove][y][x];
6778             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6779             if(p != EmptySquare) {
6780                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6781                 fromX = fromY = -1;
6782                 return;
6783             }
6784         }
6785         DrawPosition(FALSE, boards[currentMove]);
6786         return;
6787     }
6788
6789     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6790     if(clickType == Press
6791             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6792               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6793               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6794         return;
6795
6796     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6797         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6798
6799     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6800         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6801                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6802         defaultPromoChoice = DefaultPromoChoice(side);
6803     }
6804
6805     autoQueen = appData.alwaysPromoteToQueen;
6806
6807     if (fromX == -1) {
6808       int originalY = y;
6809       gatingPiece = EmptySquare;
6810       if (clickType != Press) {
6811         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6812             DragPieceEnd(xPix, yPix); dragging = 0;
6813             DrawPosition(FALSE, NULL);
6814         }
6815         return;
6816       }
6817       fromX = x; fromY = y;
6818       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6819          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6820          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6821             /* First square */
6822             if (OKToStartUserMove(fromX, fromY)) {
6823                 second = 0;
6824                 MarkTargetSquares(0);
6825                 DragPieceBegin(xPix, yPix); dragging = 1;
6826                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6827                     promoSweep = defaultPromoChoice;
6828                     selectFlag = 0; lastX = xPix; lastY = yPix;
6829                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6830                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6831                 }
6832                 if (appData.highlightDragging) {
6833                     SetHighlights(fromX, fromY, -1, -1);
6834                 }
6835             } else fromX = fromY = -1;
6836             return;
6837         }
6838     }
6839
6840     /* fromX != -1 */
6841     if (clickType == Press && gameMode != EditPosition) {
6842         ChessSquare fromP;
6843         ChessSquare toP;
6844         int frc;
6845
6846         // ignore off-board to clicks
6847         if(y < 0 || x < 0) return;
6848
6849         /* Check if clicking again on the same color piece */
6850         fromP = boards[currentMove][fromY][fromX];
6851         toP = boards[currentMove][y][x];
6852         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6853         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6854              WhitePawn <= toP && toP <= WhiteKing &&
6855              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6856              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6857             (BlackPawn <= fromP && fromP <= BlackKing &&
6858              BlackPawn <= toP && toP <= BlackKing &&
6859              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6860              !(fromP == BlackKing && toP == BlackRook && frc))) {
6861             /* Clicked again on same color piece -- changed his mind */
6862             second = (x == fromX && y == fromY);
6863             promoDefaultAltered = FALSE;
6864            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6865             if (appData.highlightDragging) {
6866                 SetHighlights(x, y, -1, -1);
6867             } else {
6868                 ClearHighlights();
6869             }
6870             if (OKToStartUserMove(x, y)) {
6871                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6872                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6873                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6874                  gatingPiece = boards[currentMove][fromY][fromX];
6875                 else gatingPiece = EmptySquare;
6876                 fromX = x;
6877                 fromY = y; dragging = 1;
6878                 MarkTargetSquares(0);
6879                 DragPieceBegin(xPix, yPix);
6880                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6881                     promoSweep = defaultPromoChoice;
6882                     selectFlag = 0; lastX = xPix; lastY = yPix;
6883                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6884                 }
6885             }
6886            }
6887            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6888            second = FALSE; 
6889         }
6890         // ignore clicks on holdings
6891         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6892     }
6893
6894     if (clickType == Release && x == fromX && y == fromY) {
6895         DragPieceEnd(xPix, yPix); dragging = 0;
6896         if(clearFlag) {
6897             // a deferred attempt to click-click move an empty square on top of a piece
6898             boards[currentMove][y][x] = EmptySquare;
6899             ClearHighlights();
6900             DrawPosition(FALSE, boards[currentMove]);
6901             fromX = fromY = -1; clearFlag = 0;
6902             return;
6903         }
6904         if (appData.animateDragging) {
6905             /* Undo animation damage if any */
6906             DrawPosition(FALSE, NULL);
6907         }
6908         if (second) {
6909             /* Second up/down in same square; just abort move */
6910             second = 0;
6911             fromX = fromY = -1;
6912             gatingPiece = EmptySquare;
6913             ClearHighlights();
6914             gotPremove = 0;
6915             ClearPremoveHighlights();
6916         } else {
6917             /* First upclick in same square; start click-click mode */
6918             SetHighlights(x, y, -1, -1);
6919         }
6920         return;
6921     }
6922
6923     clearFlag = 0;
6924
6925     /* we now have a different from- and (possibly off-board) to-square */
6926     /* Completed move */
6927     toX = x;
6928     toY = y;
6929     saveAnimate = appData.animate;
6930     if (clickType == Press) {
6931         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6932             // must be Edit Position mode with empty-square selected
6933             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6934             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6935             return;
6936         }
6937         /* Finish clickclick move */
6938         if (appData.animate || appData.highlightLastMove) {
6939             SetHighlights(fromX, fromY, toX, toY);
6940         } else {
6941             ClearHighlights();
6942         }
6943     } else {
6944         /* Finish drag move */
6945         if (appData.highlightLastMove) {
6946             SetHighlights(fromX, fromY, toX, toY);
6947         } else {
6948             ClearHighlights();
6949         }
6950         DragPieceEnd(xPix, yPix); dragging = 0;
6951         /* Don't animate move and drag both */
6952         appData.animate = FALSE;
6953     }
6954
6955     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6956     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6957         ChessSquare piece = boards[currentMove][fromY][fromX];
6958         if(gameMode == EditPosition && piece != EmptySquare &&
6959            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6960             int n;
6961
6962             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6963                 n = PieceToNumber(piece - (int)BlackPawn);
6964                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6965                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6966                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6967             } else
6968             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6969                 n = PieceToNumber(piece);
6970                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6971                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6972                 boards[currentMove][n][BOARD_WIDTH-2]++;
6973             }
6974             boards[currentMove][fromY][fromX] = EmptySquare;
6975         }
6976         ClearHighlights();
6977         fromX = fromY = -1;
6978         DrawPosition(TRUE, boards[currentMove]);
6979         return;
6980     }
6981
6982     // off-board moves should not be highlighted
6983     if(x < 0 || y < 0) ClearHighlights();
6984
6985     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6986
6987     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6988         SetHighlights(fromX, fromY, toX, toY);
6989         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6990             // [HGM] super: promotion to captured piece selected from holdings
6991             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6992             promotionChoice = TRUE;
6993             // kludge follows to temporarily execute move on display, without promoting yet
6994             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6995             boards[currentMove][toY][toX] = p;
6996             DrawPosition(FALSE, boards[currentMove]);
6997             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6998             boards[currentMove][toY][toX] = q;
6999             DisplayMessage("Click in holdings to choose piece", "");
7000             return;
7001         }
7002         PromotionPopUp();
7003     } else {
7004         int oldMove = currentMove;
7005         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7006         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7007         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7008         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7009            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7010             DrawPosition(TRUE, boards[currentMove]);
7011         fromX = fromY = -1;
7012     }
7013     appData.animate = saveAnimate;
7014     if (appData.animate || appData.animateDragging) {
7015         /* Undo animation damage if needed */
7016         DrawPosition(FALSE, NULL);
7017     }
7018 }
7019
7020 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7021 {   // front-end-free part taken out of PieceMenuPopup
7022     int whichMenu; int xSqr, ySqr;
7023
7024     if(seekGraphUp) { // [HGM] seekgraph
7025         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7026         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7027         return -2;
7028     }
7029
7030     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7031          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7032         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7033         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7034         if(action == Press)   {
7035             originalFlip = flipView;
7036             flipView = !flipView; // temporarily flip board to see game from partners perspective
7037             DrawPosition(TRUE, partnerBoard);
7038             DisplayMessage(partnerStatus, "");
7039             partnerUp = TRUE;
7040         } else if(action == Release) {
7041             flipView = originalFlip;
7042             DrawPosition(TRUE, boards[currentMove]);
7043             partnerUp = FALSE;
7044         }
7045         return -2;
7046     }
7047
7048     xSqr = EventToSquare(x, BOARD_WIDTH);
7049     ySqr = EventToSquare(y, BOARD_HEIGHT);
7050     if (action == Release) {
7051         if(pieceSweep != EmptySquare) {
7052             EditPositionMenuEvent(pieceSweep, toX, toY);
7053             pieceSweep = EmptySquare;
7054         } else UnLoadPV(); // [HGM] pv
7055     }
7056     if (action != Press) return -2; // return code to be ignored
7057     switch (gameMode) {
7058       case IcsExamining:
7059         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7060       case EditPosition:
7061         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7062         if (xSqr < 0 || ySqr < 0) return -1;
7063         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7064         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7065         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7066         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7067         NextPiece(0);
7068         return -2;\r
7069       case IcsObserving:
7070         if(!appData.icsEngineAnalyze) return -1;
7071       case IcsPlayingWhite:
7072       case IcsPlayingBlack:
7073         if(!appData.zippyPlay) goto noZip;
7074       case AnalyzeMode:
7075       case AnalyzeFile:
7076       case MachinePlaysWhite:
7077       case MachinePlaysBlack:
7078       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7079         if (!appData.dropMenu) {
7080           LoadPV(x, y);
7081           return 2; // flag front-end to grab mouse events
7082         }
7083         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7084            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7085       case EditGame:
7086       noZip:
7087         if (xSqr < 0 || ySqr < 0) return -1;
7088         if (!appData.dropMenu || appData.testLegality &&
7089             gameInfo.variant != VariantBughouse &&
7090             gameInfo.variant != VariantCrazyhouse) return -1;
7091         whichMenu = 1; // drop menu
7092         break;
7093       default:
7094         return -1;
7095     }
7096
7097     if (((*fromX = xSqr) < 0) ||
7098         ((*fromY = ySqr) < 0)) {
7099         *fromX = *fromY = -1;
7100         return -1;
7101     }
7102     if (flipView)
7103       *fromX = BOARD_WIDTH - 1 - *fromX;
7104     else
7105       *fromY = BOARD_HEIGHT - 1 - *fromY;
7106
7107     return whichMenu;
7108 }
7109
7110 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7111 {
7112 //    char * hint = lastHint;
7113     FrontEndProgramStats stats;
7114
7115     stats.which = cps == &first ? 0 : 1;
7116     stats.depth = cpstats->depth;
7117     stats.nodes = cpstats->nodes;
7118     stats.score = cpstats->score;
7119     stats.time = cpstats->time;
7120     stats.pv = cpstats->movelist;
7121     stats.hint = lastHint;
7122     stats.an_move_index = 0;
7123     stats.an_move_count = 0;
7124
7125     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7126         stats.hint = cpstats->move_name;
7127         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7128         stats.an_move_count = cpstats->nr_moves;
7129     }
7130
7131     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
7132
7133     SetProgramStats( &stats );
7134 }
7135
7136 #define MAXPLAYERS 500
7137
7138 char *
7139 TourneyStandings(int display)
7140 {
7141     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7142     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7143     char result, *p, *names[MAXPLAYERS];
7144
7145     names[0] = p = strdup(appData.participants);
7146     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7147
7148     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7149
7150     while(result = appData.results[nr]) {
7151         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7152         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7153         wScore = bScore = 0;
7154         switch(result) {
7155           case '+': wScore = 2; break;
7156           case '-': bScore = 2; break;
7157           case '=': wScore = bScore = 1; break;
7158           case ' ':
7159           case '*': return strdup("busy"); // tourney not finished
7160         }
7161         score[w] += wScore;
7162         score[b] += bScore;
7163         games[w]++;
7164         games[b]++;
7165         nr++;
7166     }
7167     if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7168     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7169     for(w=0; w<nPlayers; w++) {
7170         bScore = -1;
7171         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7172         ranking[w] = b; points[w] = bScore; score[b] = -2;
7173     }
7174     p = malloc(nPlayers*34+1);
7175     for(w=0; w<nPlayers && w<display; w++)
7176         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7177     free(names[0]);
7178     return p;
7179 }
7180
7181 void
7182 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7183 {       // count all piece types
7184         int p, f, r;
7185         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7186         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7187         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7188                 p = board[r][f];
7189                 pCnt[p]++;
7190                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7191                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7192                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7193                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7194                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7195                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7196         }
7197 }
7198
7199 int
7200 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7201 {
7202         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7203         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7204
7205         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7206         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7207         if(myPawns == 2 && nMine == 3) // KPP
7208             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7209         if(myPawns == 1 && nMine == 2) // KP
7210             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7211         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7212             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7213         if(myPawns) return FALSE;
7214         if(pCnt[WhiteRook+side])
7215             return pCnt[BlackRook-side] ||
7216                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7217                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7218                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7219         if(pCnt[WhiteCannon+side]) {
7220             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7221             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7222         }
7223         if(pCnt[WhiteKnight+side])
7224             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7225         return FALSE;
7226 }
7227
7228 int
7229 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7230 {
7231         VariantClass v = gameInfo.variant;
7232
7233         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7234         if(v == VariantShatranj) return TRUE; // always winnable through baring
7235         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7236         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7237
7238         if(v == VariantXiangqi) {
7239                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7240
7241                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7242                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7243                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7244                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7245                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7246                 if(stale) // we have at least one last-rank P plus perhaps C
7247                     return majors // KPKX
7248                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7249                 else // KCA*E*
7250                     return pCnt[WhiteFerz+side] // KCAK
7251                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7252                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7253                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7254
7255         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7256                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7257
7258                 if(nMine == 1) return FALSE; // bare King
7259                 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
7260                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7261                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7262                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7263                 if(pCnt[WhiteKnight+side])
7264                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7265                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7266                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7267                 if(nBishops)
7268                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7269                 if(pCnt[WhiteAlfil+side])
7270                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7271                 if(pCnt[WhiteWazir+side])
7272                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7273         }
7274
7275         return TRUE;
7276 }
7277
7278 int
7279 Adjudicate(ChessProgramState *cps)
7280 {       // [HGM] some adjudications useful with buggy engines
7281         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7282         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7283         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7284         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7285         int k, count = 0; static int bare = 1;
7286         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7287         Boolean canAdjudicate = !appData.icsActive;
7288
7289         // most tests only when we understand the game, i.e. legality-checking on
7290             if( appData.testLegality )
7291             {   /* [HGM] Some more adjudications for obstinate engines */
7292                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7293                 static int moveCount = 6;
7294                 ChessMove result;
7295                 char *reason = NULL;
7296
7297                 /* Count what is on board. */
7298                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7299
7300                 /* Some material-based adjudications that have to be made before stalemate test */
7301                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7302                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7303                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7304                      if(canAdjudicate && appData.checkMates) {
7305                          if(engineOpponent)
7306                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7307                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7308                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7309                          return 1;
7310                      }
7311                 }
7312
7313                 /* Bare King in Shatranj (loses) or Losers (wins) */
7314                 if( nrW == 1 || nrB == 1) {
7315                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7316                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7317                      if(canAdjudicate && appData.checkMates) {
7318                          if(engineOpponent)
7319                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7320                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7321                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7322                          return 1;
7323                      }
7324                   } else
7325                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7326                   {    /* bare King */
7327                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7328                         if(canAdjudicate && appData.checkMates) {
7329                             /* but only adjudicate if adjudication enabled */
7330                             if(engineOpponent)
7331                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7332                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7333                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7334                             return 1;
7335                         }
7336                   }
7337                 } else bare = 1;
7338
7339
7340             // don't wait for engine to announce game end if we can judge ourselves
7341             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7342               case MT_CHECK:
7343                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7344                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7345                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7346                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7347                             checkCnt++;
7348                         if(checkCnt >= 2) {
7349                             reason = "Xboard adjudication: 3rd check";
7350                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7351                             break;
7352                         }
7353                     }
7354                 }
7355               case MT_NONE:
7356               default:
7357                 break;
7358               case MT_STALEMATE:
7359               case MT_STAINMATE:
7360                 reason = "Xboard adjudication: Stalemate";
7361                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7362                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7363                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7364                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7365                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7366                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7367                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7368                                                                         EP_CHECKMATE : EP_WINS);
7369                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7370                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7371                 }
7372                 break;
7373               case MT_CHECKMATE:
7374                 reason = "Xboard adjudication: Checkmate";
7375                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7376                 break;
7377             }
7378
7379                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7380                     case EP_STALEMATE:
7381                         result = GameIsDrawn; break;
7382                     case EP_CHECKMATE:
7383                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7384                     case EP_WINS:
7385                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7386                     default:
7387                         result = EndOfFile;
7388                 }
7389                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7390                     if(engineOpponent)
7391                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7392                     GameEnds( result, reason, GE_XBOARD );
7393                     return 1;
7394                 }
7395
7396                 /* Next absolutely insufficient mating material. */
7397                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7398                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7399                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7400
7401                      /* always flag draws, for judging claims */
7402                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7403
7404                      if(canAdjudicate && appData.materialDraws) {
7405                          /* but only adjudicate them if adjudication enabled */
7406                          if(engineOpponent) {
7407                            SendToProgram("force\n", engineOpponent); // suppress reply
7408                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7409                          }
7410                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7411                          return 1;
7412                      }
7413                 }
7414
7415                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7416                 if(gameInfo.variant == VariantXiangqi ?
7417                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7418                  : nrW + nrB == 4 &&
7419                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7420                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7421                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7422                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7423                    ) ) {
7424                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7425                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7426                           if(engineOpponent) {
7427                             SendToProgram("force\n", engineOpponent); // suppress reply
7428                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7429                           }
7430                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7431                           return 1;
7432                      }
7433                 } else moveCount = 6;
7434             }
7435         if (appData.debugMode) { int i;
7436             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7437                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7438                     appData.drawRepeats);
7439             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7440               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7441
7442         }
7443
7444         // Repetition draws and 50-move rule can be applied independently of legality testing
7445
7446                 /* Check for rep-draws */
7447                 count = 0;
7448                 for(k = forwardMostMove-2;
7449                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7450                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7451                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7452                     k-=2)
7453                 {   int rights=0;
7454                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7455                         /* compare castling rights */
7456                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7457                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7458                                 rights++; /* King lost rights, while rook still had them */
7459                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7460                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7461                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7462                                    rights++; /* but at least one rook lost them */
7463                         }
7464                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7465                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7466                                 rights++;
7467                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7468                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7469                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7470                                    rights++;
7471                         }
7472                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7473                             && appData.drawRepeats > 1) {
7474                              /* adjudicate after user-specified nr of repeats */
7475                              int result = GameIsDrawn;
7476                              char *details = "XBoard adjudication: repetition draw";
7477                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7478                                 // [HGM] xiangqi: check for forbidden perpetuals
7479                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7480                                 for(m=forwardMostMove; m>k; m-=2) {
7481                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7482                                         ourPerpetual = 0; // the current mover did not always check
7483                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7484                                         hisPerpetual = 0; // the opponent did not always check
7485                                 }
7486                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7487                                                                         ourPerpetual, hisPerpetual);
7488                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7489                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7490                                     details = "Xboard adjudication: perpetual checking";
7491                                 } else
7492                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7493                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7494                                 } else
7495                                 // Now check for perpetual chases
7496                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7497                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7498                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7499                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7500                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7501                                         details = "Xboard adjudication: perpetual chasing";
7502                                     } else
7503                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7504                                         break; // Abort repetition-checking loop.
7505                                 }
7506                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7507                              }
7508                              if(engineOpponent) {
7509                                SendToProgram("force\n", engineOpponent); // suppress reply
7510                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7511                              }
7512                              GameEnds( result, details, GE_XBOARD );
7513                              return 1;
7514                         }
7515                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7516                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7517                     }
7518                 }
7519
7520                 /* Now we test for 50-move draws. Determine ply count */
7521                 count = forwardMostMove;
7522                 /* look for last irreversble move */
7523                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7524                     count--;
7525                 /* if we hit starting position, add initial plies */
7526                 if( count == backwardMostMove )
7527                     count -= initialRulePlies;
7528                 count = forwardMostMove - count;
7529                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7530                         // adjust reversible move counter for checks in Xiangqi
7531                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7532                         if(i < backwardMostMove) i = backwardMostMove;
7533                         while(i <= forwardMostMove) {
7534                                 lastCheck = inCheck; // check evasion does not count
7535                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7536                                 if(inCheck || lastCheck) count--; // check does not count
7537                                 i++;
7538                         }
7539                 }
7540                 if( count >= 100)
7541                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7542                          /* this is used to judge if draw claims are legal */
7543                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7544                          if(engineOpponent) {
7545                            SendToProgram("force\n", engineOpponent); // suppress reply
7546                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7547                          }
7548                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7549                          return 1;
7550                 }
7551
7552                 /* if draw offer is pending, treat it as a draw claim
7553                  * when draw condition present, to allow engines a way to
7554                  * claim draws before making their move to avoid a race
7555                  * condition occurring after their move
7556                  */
7557                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7558                          char *p = NULL;
7559                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7560                              p = "Draw claim: 50-move rule";
7561                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7562                              p = "Draw claim: 3-fold repetition";
7563                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7564                              p = "Draw claim: insufficient mating material";
7565                          if( p != NULL && canAdjudicate) {
7566                              if(engineOpponent) {
7567                                SendToProgram("force\n", engineOpponent); // suppress reply
7568                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7569                              }
7570                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7571                              return 1;
7572                          }
7573                 }
7574
7575                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7576                     if(engineOpponent) {
7577                       SendToProgram("force\n", engineOpponent); // suppress reply
7578                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7579                     }
7580                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7581                     return 1;
7582                 }
7583         return 0;
7584 }
7585
7586 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7587 {   // [HGM] book: this routine intercepts moves to simulate book replies
7588     char *bookHit = NULL;
7589
7590     //first determine if the incoming move brings opponent into his book
7591     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7592         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7593     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7594     if(bookHit != NULL && !cps->bookSuspend) {
7595         // make sure opponent is not going to reply after receiving move to book position
7596         SendToProgram("force\n", cps);
7597         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7598     }
7599     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7600     // now arrange restart after book miss
7601     if(bookHit) {
7602         // after a book hit we never send 'go', and the code after the call to this routine
7603         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7604         char buf[MSG_SIZ], *move = bookHit;
7605         if(cps->useSAN) {
7606             int fromX, fromY, toX, toY;
7607             char promoChar;
7608             ChessMove moveType;
7609             move = buf + 30;
7610             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7611                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7612                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7613                                     PosFlags(forwardMostMove),
7614                                     fromY, fromX, toY, toX, promoChar, move);
7615             } else {
7616                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7617                 bookHit = NULL;
7618             }
7619         }
7620         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7621         SendToProgram(buf, cps);
7622         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7623     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7624         SendToProgram("go\n", cps);
7625         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7626     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7627         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7628             SendToProgram("go\n", cps);
7629         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7630     }
7631     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7632 }
7633
7634 char *savedMessage;
7635 ChessProgramState *savedState;
7636 void DeferredBookMove(void)
7637 {
7638         if(savedState->lastPing != savedState->lastPong)
7639                     ScheduleDelayedEvent(DeferredBookMove, 10);
7640         else
7641         HandleMachineMove(savedMessage, savedState);
7642 }
7643
7644 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7645
7646 void
7647 HandleMachineMove(message, cps)
7648      char *message;
7649      ChessProgramState *cps;
7650 {
7651     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7652     char realname[MSG_SIZ];
7653     int fromX, fromY, toX, toY;
7654     ChessMove moveType;
7655     char promoChar;
7656     char *p;
7657     int machineWhite;
7658     char *bookHit;
7659
7660     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7661         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7662         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
7663         pairingReceived = 1;
7664         NextMatchGame();
7665         return; // Skim the pairing messages here.
7666     }
7667
7668     cps->userError = 0;
7669
7670 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7671     /*
7672      * Kludge to ignore BEL characters
7673      */
7674     while (*message == '\007') message++;
7675
7676     /*
7677      * [HGM] engine debug message: ignore lines starting with '#' character
7678      */
7679     if(cps->debug && *message == '#') return;
7680
7681     /*
7682      * Look for book output
7683      */
7684     if (cps == &first && bookRequested) {
7685         if (message[0] == '\t' || message[0] == ' ') {
7686             /* Part of the book output is here; append it */
7687             strcat(bookOutput, message);
7688             strcat(bookOutput, "  \n");
7689             return;
7690         } else if (bookOutput[0] != NULLCHAR) {
7691             /* All of book output has arrived; display it */
7692             char *p = bookOutput;
7693             while (*p != NULLCHAR) {
7694                 if (*p == '\t') *p = ' ';
7695                 p++;
7696             }
7697             DisplayInformation(bookOutput);
7698             bookRequested = FALSE;
7699             /* Fall through to parse the current output */
7700         }
7701     }
7702
7703     /*
7704      * Look for machine move.
7705      */
7706     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7707         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7708     {
7709         /* This method is only useful on engines that support ping */
7710         if (cps->lastPing != cps->lastPong) {
7711           if (gameMode == BeginningOfGame) {
7712             /* Extra move from before last new; ignore */
7713             if (appData.debugMode) {
7714                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7715             }
7716           } else {
7717             if (appData.debugMode) {
7718                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7719                         cps->which, gameMode);
7720             }
7721
7722             SendToProgram("undo\n", cps);
7723           }
7724           return;
7725         }
7726
7727         switch (gameMode) {
7728           case BeginningOfGame:
7729             /* Extra move from before last reset; ignore */
7730             if (appData.debugMode) {
7731                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7732             }
7733             return;
7734
7735           case EndOfGame:
7736           case IcsIdle:
7737           default:
7738             /* Extra move after we tried to stop.  The mode test is
7739                not a reliable way of detecting this problem, but it's
7740                the best we can do on engines that don't support ping.
7741             */
7742             if (appData.debugMode) {
7743                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7744                         cps->which, gameMode);
7745             }
7746             SendToProgram("undo\n", cps);
7747             return;
7748
7749           case MachinePlaysWhite:
7750           case IcsPlayingWhite:
7751             machineWhite = TRUE;
7752             break;
7753
7754           case MachinePlaysBlack:
7755           case IcsPlayingBlack:
7756             machineWhite = FALSE;
7757             break;
7758
7759           case TwoMachinesPlay:
7760             machineWhite = (cps->twoMachinesColor[0] == 'w');
7761             break;
7762         }
7763         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7764             if (appData.debugMode) {
7765                 fprintf(debugFP,
7766                         "Ignoring move out of turn by %s, gameMode %d"
7767                         ", forwardMost %d\n",
7768                         cps->which, gameMode, forwardMostMove);
7769             }
7770             return;
7771         }
7772
7773     if (appData.debugMode) { int f = forwardMostMove;
7774         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7775                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7776                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7777     }
7778         if(cps->alphaRank) AlphaRank(machineMove, 4);
7779         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7780                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7781             /* Machine move could not be parsed; ignore it. */
7782           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7783                     machineMove, _(cps->which));
7784             DisplayError(buf1, 0);
7785             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7786                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7787             if (gameMode == TwoMachinesPlay) {
7788               GameEnds(machineWhite ? BlackWins : WhiteWins,
7789                        buf1, GE_XBOARD);
7790             }
7791             return;
7792         }
7793
7794         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7795         /* So we have to redo legality test with true e.p. status here,  */
7796         /* to make sure an illegal e.p. capture does not slip through,   */
7797         /* to cause a forfeit on a justified illegal-move complaint      */
7798         /* of the opponent.                                              */
7799         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7800            ChessMove moveType;
7801            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7802                              fromY, fromX, toY, toX, promoChar);
7803             if (appData.debugMode) {
7804                 int i;
7805                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7806                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7807                 fprintf(debugFP, "castling rights\n");
7808             }
7809             if(moveType == IllegalMove) {
7810               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7811                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7812                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7813                            buf1, GE_XBOARD);
7814                 return;
7815            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7816            /* [HGM] Kludge to handle engines that send FRC-style castling
7817               when they shouldn't (like TSCP-Gothic) */
7818            switch(moveType) {
7819              case WhiteASideCastleFR:
7820              case BlackASideCastleFR:
7821                toX+=2;
7822                currentMoveString[2]++;
7823                break;
7824              case WhiteHSideCastleFR:
7825              case BlackHSideCastleFR:
7826                toX--;
7827                currentMoveString[2]--;
7828                break;
7829              default: ; // nothing to do, but suppresses warning of pedantic compilers
7830            }
7831         }
7832         hintRequested = FALSE;
7833         lastHint[0] = NULLCHAR;
7834         bookRequested = FALSE;
7835         /* Program may be pondering now */
7836         cps->maybeThinking = TRUE;
7837         if (cps->sendTime == 2) cps->sendTime = 1;
7838         if (cps->offeredDraw) cps->offeredDraw--;
7839
7840         /* [AS] Save move info*/
7841         pvInfoList[ forwardMostMove ].score = programStats.score;
7842         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7843         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7844
7845         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7846
7847         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7848         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7849             int count = 0;
7850
7851             while( count < adjudicateLossPlies ) {
7852                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7853
7854                 if( count & 1 ) {
7855                     score = -score; /* Flip score for winning side */
7856                 }
7857
7858                 if( score > adjudicateLossThreshold ) {
7859                     break;
7860                 }
7861
7862                 count++;
7863             }
7864
7865             if( count >= adjudicateLossPlies ) {
7866                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7867
7868                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7869                     "Xboard adjudication",
7870                     GE_XBOARD );
7871
7872                 return;
7873             }
7874         }
7875
7876         if(Adjudicate(cps)) {
7877             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7878             return; // [HGM] adjudicate: for all automatic game ends
7879         }
7880
7881 #if ZIPPY
7882         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7883             first.initDone) {
7884           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7885                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7886                 SendToICS("draw ");
7887                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7888           }
7889           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7890           ics_user_moved = 1;
7891           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7892                 char buf[3*MSG_SIZ];
7893
7894                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7895                         programStats.score / 100.,
7896                         programStats.depth,
7897                         programStats.time / 100.,
7898                         (unsigned int)programStats.nodes,
7899                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7900                         programStats.movelist);
7901                 SendToICS(buf);
7902 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7903           }
7904         }
7905 #endif
7906
7907         /* [AS] Clear stats for next move */
7908         ClearProgramStats();
7909         thinkOutput[0] = NULLCHAR;
7910         hiddenThinkOutputState = 0;
7911
7912         bookHit = NULL;
7913         if (gameMode == TwoMachinesPlay) {
7914             /* [HGM] relaying draw offers moved to after reception of move */
7915             /* and interpreting offer as claim if it brings draw condition */
7916             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7917                 SendToProgram("draw\n", cps->other);
7918             }
7919             if (cps->other->sendTime) {
7920                 SendTimeRemaining(cps->other,
7921                                   cps->other->twoMachinesColor[0] == 'w');
7922             }
7923             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7924             if (firstMove && !bookHit) {
7925                 firstMove = FALSE;
7926                 if (cps->other->useColors) {
7927                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7928                 }
7929                 SendToProgram("go\n", cps->other);
7930             }
7931             cps->other->maybeThinking = TRUE;
7932         }
7933
7934         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7935
7936         if (!pausing && appData.ringBellAfterMoves) {
7937             RingBell();
7938         }
7939
7940         /*
7941          * Reenable menu items that were disabled while
7942          * machine was thinking
7943          */
7944         if (gameMode != TwoMachinesPlay)
7945             SetUserThinkingEnables();
7946
7947         // [HGM] book: after book hit opponent has received move and is now in force mode
7948         // force the book reply into it, and then fake that it outputted this move by jumping
7949         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7950         if(bookHit) {
7951                 static char bookMove[MSG_SIZ]; // a bit generous?
7952
7953                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7954                 strcat(bookMove, bookHit);
7955                 message = bookMove;
7956                 cps = cps->other;
7957                 programStats.nodes = programStats.depth = programStats.time =
7958                 programStats.score = programStats.got_only_move = 0;
7959                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7960
7961                 if(cps->lastPing != cps->lastPong) {
7962                     savedMessage = message; // args for deferred call
7963                     savedState = cps;
7964                     ScheduleDelayedEvent(DeferredBookMove, 10);
7965                     return;
7966                 }
7967                 goto FakeBookMove;
7968         }
7969
7970         return;
7971     }
7972
7973     /* Set special modes for chess engines.  Later something general
7974      *  could be added here; for now there is just one kludge feature,
7975      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7976      *  when "xboard" is given as an interactive command.
7977      */
7978     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7979         cps->useSigint = FALSE;
7980         cps->useSigterm = FALSE;
7981     }
7982     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7983       ParseFeatures(message+8, cps);
7984       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7985     }
7986
7987     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7988       int dummy, s=6; char buf[MSG_SIZ];
7989       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7990       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7991       ParseFEN(boards[0], &dummy, message+s);
7992       DrawPosition(TRUE, boards[0]);
7993       startedFromSetupPosition = TRUE;
7994       return;
7995     }
7996     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7997      * want this, I was asked to put it in, and obliged.
7998      */
7999     if (!strncmp(message, "setboard ", 9)) {
8000         Board initial_position;
8001
8002         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8003
8004         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8005             DisplayError(_("Bad FEN received from engine"), 0);
8006             return ;
8007         } else {
8008            Reset(TRUE, FALSE);
8009            CopyBoard(boards[0], initial_position);
8010            initialRulePlies = FENrulePlies;
8011            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8012            else gameMode = MachinePlaysBlack;
8013            DrawPosition(FALSE, boards[currentMove]);
8014         }
8015         return;
8016     }
8017
8018     /*
8019      * Look for communication commands
8020      */
8021     if (!strncmp(message, "telluser ", 9)) {
8022         if(message[9] == '\\' && message[10] == '\\')
8023             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8024         DisplayNote(message + 9);
8025         return;
8026     }
8027     if (!strncmp(message, "tellusererror ", 14)) {
8028         cps->userError = 1;
8029         if(message[14] == '\\' && message[15] == '\\')
8030             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8031         DisplayError(message + 14, 0);
8032         return;
8033     }
8034     if (!strncmp(message, "tellopponent ", 13)) {
8035       if (appData.icsActive) {
8036         if (loggedOn) {
8037           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8038           SendToICS(buf1);
8039         }
8040       } else {
8041         DisplayNote(message + 13);
8042       }
8043       return;
8044     }
8045     if (!strncmp(message, "tellothers ", 11)) {
8046       if (appData.icsActive) {
8047         if (loggedOn) {
8048           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8049           SendToICS(buf1);
8050         }
8051       }
8052       return;
8053     }
8054     if (!strncmp(message, "tellall ", 8)) {
8055       if (appData.icsActive) {
8056         if (loggedOn) {
8057           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8058           SendToICS(buf1);
8059         }
8060       } else {
8061         DisplayNote(message + 8);
8062       }
8063       return;
8064     }
8065     if (strncmp(message, "warning", 7) == 0) {
8066         /* Undocumented feature, use tellusererror in new code */
8067         DisplayError(message, 0);
8068         return;
8069     }
8070     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8071         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8072         strcat(realname, " query");
8073         AskQuestion(realname, buf2, buf1, cps->pr);
8074         return;
8075     }
8076     /* Commands from the engine directly to ICS.  We don't allow these to be
8077      *  sent until we are logged on. Crafty kibitzes have been known to
8078      *  interfere with the login process.
8079      */
8080     if (loggedOn) {
8081         if (!strncmp(message, "tellics ", 8)) {
8082             SendToICS(message + 8);
8083             SendToICS("\n");
8084             return;
8085         }
8086         if (!strncmp(message, "tellicsnoalias ", 15)) {
8087             SendToICS(ics_prefix);
8088             SendToICS(message + 15);
8089             SendToICS("\n");
8090             return;
8091         }
8092         /* The following are for backward compatibility only */
8093         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8094             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8095             SendToICS(ics_prefix);
8096             SendToICS(message);
8097             SendToICS("\n");
8098             return;
8099         }
8100     }
8101     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8102         return;
8103     }
8104     /*
8105      * If the move is illegal, cancel it and redraw the board.
8106      * Also deal with other error cases.  Matching is rather loose
8107      * here to accommodate engines written before the spec.
8108      */
8109     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8110         strncmp(message, "Error", 5) == 0) {
8111         if (StrStr(message, "name") ||
8112             StrStr(message, "rating") || StrStr(message, "?") ||
8113             StrStr(message, "result") || StrStr(message, "board") ||
8114             StrStr(message, "bk") || StrStr(message, "computer") ||
8115             StrStr(message, "variant") || StrStr(message, "hint") ||
8116             StrStr(message, "random") || StrStr(message, "depth") ||
8117             StrStr(message, "accepted")) {
8118             return;
8119         }
8120         if (StrStr(message, "protover")) {
8121           /* Program is responding to input, so it's apparently done
8122              initializing, and this error message indicates it is
8123              protocol version 1.  So we don't need to wait any longer
8124              for it to initialize and send feature commands. */
8125           FeatureDone(cps, 1);
8126           cps->protocolVersion = 1;
8127           return;
8128         }
8129         cps->maybeThinking = FALSE;
8130
8131         if (StrStr(message, "draw")) {
8132             /* Program doesn't have "draw" command */
8133             cps->sendDrawOffers = 0;
8134             return;
8135         }
8136         if (cps->sendTime != 1 &&
8137             (StrStr(message, "time") || StrStr(message, "otim"))) {
8138           /* Program apparently doesn't have "time" or "otim" command */
8139           cps->sendTime = 0;
8140           return;
8141         }
8142         if (StrStr(message, "analyze")) {
8143             cps->analysisSupport = FALSE;
8144             cps->analyzing = FALSE;
8145             Reset(FALSE, TRUE);
8146             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8147             DisplayError(buf2, 0);
8148             return;
8149         }
8150         if (StrStr(message, "(no matching move)st")) {
8151           /* Special kludge for GNU Chess 4 only */
8152           cps->stKludge = TRUE;
8153           SendTimeControl(cps, movesPerSession, timeControl,
8154                           timeIncrement, appData.searchDepth,
8155                           searchTime);
8156           return;
8157         }
8158         if (StrStr(message, "(no matching move)sd")) {
8159           /* Special kludge for GNU Chess 4 only */
8160           cps->sdKludge = TRUE;
8161           SendTimeControl(cps, movesPerSession, timeControl,
8162                           timeIncrement, appData.searchDepth,
8163                           searchTime);
8164           return;
8165         }
8166         if (!StrStr(message, "llegal")) {
8167             return;
8168         }
8169         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8170             gameMode == IcsIdle) return;
8171         if (forwardMostMove <= backwardMostMove) return;
8172         if (pausing) PauseEvent();
8173       if(appData.forceIllegal) {
8174             // [HGM] illegal: machine refused move; force position after move into it
8175           SendToProgram("force\n", cps);
8176           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8177                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8178                 // when black is to move, while there might be nothing on a2 or black
8179                 // might already have the move. So send the board as if white has the move.
8180                 // But first we must change the stm of the engine, as it refused the last move
8181                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8182                 if(WhiteOnMove(forwardMostMove)) {
8183                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8184                     SendBoard(cps, forwardMostMove); // kludgeless board
8185                 } else {
8186                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8187                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8188                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8189                 }
8190           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8191             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8192                  gameMode == TwoMachinesPlay)
8193               SendToProgram("go\n", cps);
8194             return;
8195       } else
8196         if (gameMode == PlayFromGameFile) {
8197             /* Stop reading this game file */
8198             gameMode = EditGame;
8199             ModeHighlight();
8200         }
8201         /* [HGM] illegal-move claim should forfeit game when Xboard */
8202         /* only passes fully legal moves                            */
8203         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8204             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8205                                 "False illegal-move claim", GE_XBOARD );
8206             return; // do not take back move we tested as valid
8207         }
8208         currentMove = forwardMostMove-1;
8209         DisplayMove(currentMove-1); /* before DisplayMoveError */
8210         SwitchClocks(forwardMostMove-1); // [HGM] race
8211         DisplayBothClocks();
8212         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8213                 parseList[currentMove], _(cps->which));
8214         DisplayMoveError(buf1);
8215         DrawPosition(FALSE, boards[currentMove]);
8216         return;
8217     }
8218     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8219         /* Program has a broken "time" command that
8220            outputs a string not ending in newline.
8221            Don't use it. */
8222         cps->sendTime = 0;
8223     }
8224
8225     /*
8226      * If chess program startup fails, exit with an error message.
8227      * Attempts to recover here are futile.
8228      */
8229     if ((StrStr(message, "unknown host") != NULL)
8230         || (StrStr(message, "No remote directory") != NULL)
8231         || (StrStr(message, "not found") != NULL)
8232         || (StrStr(message, "No such file") != NULL)
8233         || (StrStr(message, "can't alloc") != NULL)
8234         || (StrStr(message, "Permission denied") != NULL)) {
8235
8236         cps->maybeThinking = FALSE;
8237         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8238                 _(cps->which), cps->program, cps->host, message);
8239         RemoveInputSource(cps->isr);
8240         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8241             if(cps == &first) appData.noChessProgram = TRUE;
8242             DisplayError(buf1, 0);
8243         }
8244         return;
8245     }
8246
8247     /*
8248      * Look for hint output
8249      */
8250     if (sscanf(message, "Hint: %s", buf1) == 1) {
8251         if (cps == &first && hintRequested) {
8252             hintRequested = FALSE;
8253             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8254                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8255                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8256                                     PosFlags(forwardMostMove),
8257                                     fromY, fromX, toY, toX, promoChar, buf1);
8258                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8259                 DisplayInformation(buf2);
8260             } else {
8261                 /* Hint move could not be parsed!? */
8262               snprintf(buf2, sizeof(buf2),
8263                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8264                         buf1, _(cps->which));
8265                 DisplayError(buf2, 0);
8266             }
8267         } else {
8268           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8269         }
8270         return;
8271     }
8272
8273     /*
8274      * Ignore other messages if game is not in progress
8275      */
8276     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8277         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8278
8279     /*
8280      * look for win, lose, draw, or draw offer
8281      */
8282     if (strncmp(message, "1-0", 3) == 0) {
8283         char *p, *q, *r = "";
8284         p = strchr(message, '{');
8285         if (p) {
8286             q = strchr(p, '}');
8287             if (q) {
8288                 *q = NULLCHAR;
8289                 r = p + 1;
8290             }
8291         }
8292         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8293         return;
8294     } else if (strncmp(message, "0-1", 3) == 0) {
8295         char *p, *q, *r = "";
8296         p = strchr(message, '{');
8297         if (p) {
8298             q = strchr(p, '}');
8299             if (q) {
8300                 *q = NULLCHAR;
8301                 r = p + 1;
8302             }
8303         }
8304         /* Kludge for Arasan 4.1 bug */
8305         if (strcmp(r, "Black resigns") == 0) {
8306             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8307             return;
8308         }
8309         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8310         return;
8311     } else if (strncmp(message, "1/2", 3) == 0) {
8312         char *p, *q, *r = "";
8313         p = strchr(message, '{');
8314         if (p) {
8315             q = strchr(p, '}');
8316             if (q) {
8317                 *q = NULLCHAR;
8318                 r = p + 1;
8319             }
8320         }
8321
8322         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8323         return;
8324
8325     } else if (strncmp(message, "White resign", 12) == 0) {
8326         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8327         return;
8328     } else if (strncmp(message, "Black resign", 12) == 0) {
8329         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8330         return;
8331     } else if (strncmp(message, "White matches", 13) == 0 ||
8332                strncmp(message, "Black matches", 13) == 0   ) {
8333         /* [HGM] ignore GNUShogi noises */
8334         return;
8335     } else if (strncmp(message, "White", 5) == 0 &&
8336                message[5] != '(' &&
8337                StrStr(message, "Black") == NULL) {
8338         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8339         return;
8340     } else if (strncmp(message, "Black", 5) == 0 &&
8341                message[5] != '(') {
8342         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8343         return;
8344     } else if (strcmp(message, "resign") == 0 ||
8345                strcmp(message, "computer resigns") == 0) {
8346         switch (gameMode) {
8347           case MachinePlaysBlack:
8348           case IcsPlayingBlack:
8349             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8350             break;
8351           case MachinePlaysWhite:
8352           case IcsPlayingWhite:
8353             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8354             break;
8355           case TwoMachinesPlay:
8356             if (cps->twoMachinesColor[0] == 'w')
8357               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8358             else
8359               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8360             break;
8361           default:
8362             /* can't happen */
8363             break;
8364         }
8365         return;
8366     } else if (strncmp(message, "opponent mates", 14) == 0) {
8367         switch (gameMode) {
8368           case MachinePlaysBlack:
8369           case IcsPlayingBlack:
8370             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8371             break;
8372           case MachinePlaysWhite:
8373           case IcsPlayingWhite:
8374             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8375             break;
8376           case TwoMachinesPlay:
8377             if (cps->twoMachinesColor[0] == 'w')
8378               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8379             else
8380               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8381             break;
8382           default:
8383             /* can't happen */
8384             break;
8385         }
8386         return;
8387     } else if (strncmp(message, "computer mates", 14) == 0) {
8388         switch (gameMode) {
8389           case MachinePlaysBlack:
8390           case IcsPlayingBlack:
8391             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8392             break;
8393           case MachinePlaysWhite:
8394           case IcsPlayingWhite:
8395             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8396             break;
8397           case TwoMachinesPlay:
8398             if (cps->twoMachinesColor[0] == 'w')
8399               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8400             else
8401               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8402             break;
8403           default:
8404             /* can't happen */
8405             break;
8406         }
8407         return;
8408     } else if (strncmp(message, "checkmate", 9) == 0) {
8409         if (WhiteOnMove(forwardMostMove)) {
8410             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8411         } else {
8412             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8413         }
8414         return;
8415     } else if (strstr(message, "Draw") != NULL ||
8416                strstr(message, "game is a draw") != NULL) {
8417         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8418         return;
8419     } else if (strstr(message, "offer") != NULL &&
8420                strstr(message, "draw") != NULL) {
8421 #if ZIPPY
8422         if (appData.zippyPlay && first.initDone) {
8423             /* Relay offer to ICS */
8424             SendToICS(ics_prefix);
8425             SendToICS("draw\n");
8426         }
8427 #endif
8428         cps->offeredDraw = 2; /* valid until this engine moves twice */
8429         if (gameMode == TwoMachinesPlay) {
8430             if (cps->other->offeredDraw) {
8431                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8432             /* [HGM] in two-machine mode we delay relaying draw offer      */
8433             /* until after we also have move, to see if it is really claim */
8434             }
8435         } else if (gameMode == MachinePlaysWhite ||
8436                    gameMode == MachinePlaysBlack) {
8437           if (userOfferedDraw) {
8438             DisplayInformation(_("Machine accepts your draw offer"));
8439             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8440           } else {
8441             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8442           }
8443         }
8444     }
8445
8446
8447     /*
8448      * Look for thinking output
8449      */
8450     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8451           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8452                                 ) {
8453         int plylev, mvleft, mvtot, curscore, time;
8454         char mvname[MOVE_LEN];
8455         u64 nodes; // [DM]
8456         char plyext;
8457         int ignore = FALSE;
8458         int prefixHint = FALSE;
8459         mvname[0] = NULLCHAR;
8460
8461         switch (gameMode) {
8462           case MachinePlaysBlack:
8463           case IcsPlayingBlack:
8464             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8465             break;
8466           case MachinePlaysWhite:
8467           case IcsPlayingWhite:
8468             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8469             break;
8470           case AnalyzeMode:
8471           case AnalyzeFile:
8472             break;
8473           case IcsObserving: /* [DM] icsEngineAnalyze */
8474             if (!appData.icsEngineAnalyze) ignore = TRUE;
8475             break;
8476           case TwoMachinesPlay:
8477             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8478                 ignore = TRUE;
8479             }
8480             break;
8481           default:
8482             ignore = TRUE;
8483             break;
8484         }
8485
8486         if (!ignore) {
8487             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8488             buf1[0] = NULLCHAR;
8489             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8490                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8491
8492                 if (plyext != ' ' && plyext != '\t') {
8493                     time *= 100;
8494                 }
8495
8496                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8497                 if( cps->scoreIsAbsolute &&
8498                     ( gameMode == MachinePlaysBlack ||
8499                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8500                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8501                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8502                      !WhiteOnMove(currentMove)
8503                     ) )
8504                 {
8505                     curscore = -curscore;
8506                 }
8507
8508
8509                 tempStats.depth = plylev;
8510                 tempStats.nodes = nodes;
8511                 tempStats.time = time;
8512                 tempStats.score = curscore;
8513                 tempStats.got_only_move = 0;
8514
8515                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8516                         int ticklen;
8517
8518                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8519                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8520                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8521                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8522                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8523                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8524                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8525                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8526                 }
8527
8528                 /* Buffer overflow protection */
8529                 if (buf1[0] != NULLCHAR) {
8530                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8531                         && appData.debugMode) {
8532                         fprintf(debugFP,
8533                                 "PV is too long; using the first %u bytes.\n",
8534                                 (unsigned) sizeof(tempStats.movelist) - 1);
8535                     }
8536
8537                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8538                 } else {
8539                     sprintf(tempStats.movelist, " no PV\n");
8540                 }
8541
8542                 if (tempStats.seen_stat) {
8543                     tempStats.ok_to_send = 1;
8544                 }
8545
8546                 if (strchr(tempStats.movelist, '(') != NULL) {
8547                     tempStats.line_is_book = 1;
8548                     tempStats.nr_moves = 0;
8549                     tempStats.moves_left = 0;
8550                 } else {
8551                     tempStats.line_is_book = 0;
8552                 }
8553
8554                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8555                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8556
8557                 SendProgramStatsToFrontend( cps, &tempStats );
8558
8559                 /*
8560                     [AS] Protect the thinkOutput buffer from overflow... this
8561                     is only useful if buf1 hasn't overflowed first!
8562                 */
8563                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8564                          plylev,
8565                          (gameMode == TwoMachinesPlay ?
8566                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8567                          ((double) curscore) / 100.0,
8568                          prefixHint ? lastHint : "",
8569                          prefixHint ? " " : "" );
8570
8571                 if( buf1[0] != NULLCHAR ) {
8572                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8573
8574                     if( strlen(buf1) > max_len ) {
8575                         if( appData.debugMode) {
8576                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8577                         }
8578                         buf1[max_len+1] = '\0';
8579                     }
8580
8581                     strcat( thinkOutput, buf1 );
8582                 }
8583
8584                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8585                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8586                     DisplayMove(currentMove - 1);
8587                 }
8588                 return;
8589
8590             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8591                 /* crafty (9.25+) says "(only move) <move>"
8592                  * if there is only 1 legal move
8593                  */
8594                 sscanf(p, "(only move) %s", buf1);
8595                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8596                 sprintf(programStats.movelist, "%s (only move)", buf1);
8597                 programStats.depth = 1;
8598                 programStats.nr_moves = 1;
8599                 programStats.moves_left = 1;
8600                 programStats.nodes = 1;
8601                 programStats.time = 1;
8602                 programStats.got_only_move = 1;
8603
8604                 /* Not really, but we also use this member to
8605                    mean "line isn't going to change" (Crafty
8606                    isn't searching, so stats won't change) */
8607                 programStats.line_is_book = 1;
8608
8609                 SendProgramStatsToFrontend( cps, &programStats );
8610
8611                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8612                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8613                     DisplayMove(currentMove - 1);
8614                 }
8615                 return;
8616             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8617                               &time, &nodes, &plylev, &mvleft,
8618                               &mvtot, mvname) >= 5) {
8619                 /* The stat01: line is from Crafty (9.29+) in response
8620                    to the "." command */
8621                 programStats.seen_stat = 1;
8622                 cps->maybeThinking = TRUE;
8623
8624                 if (programStats.got_only_move || !appData.periodicUpdates)
8625                   return;
8626
8627                 programStats.depth = plylev;
8628                 programStats.time = time;
8629                 programStats.nodes = nodes;
8630                 programStats.moves_left = mvleft;
8631                 programStats.nr_moves = mvtot;
8632                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8633                 programStats.ok_to_send = 1;
8634                 programStats.movelist[0] = '\0';
8635
8636                 SendProgramStatsToFrontend( cps, &programStats );
8637
8638                 return;
8639
8640             } else if (strncmp(message,"++",2) == 0) {
8641                 /* Crafty 9.29+ outputs this */
8642                 programStats.got_fail = 2;
8643                 return;
8644
8645             } else if (strncmp(message,"--",2) == 0) {
8646                 /* Crafty 9.29+ outputs this */
8647                 programStats.got_fail = 1;
8648                 return;
8649
8650             } else if (thinkOutput[0] != NULLCHAR &&
8651                        strncmp(message, "    ", 4) == 0) {
8652                 unsigned message_len;
8653
8654                 p = message;
8655                 while (*p && *p == ' ') p++;
8656
8657                 message_len = strlen( p );
8658
8659                 /* [AS] Avoid buffer overflow */
8660                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8661                     strcat(thinkOutput, " ");
8662                     strcat(thinkOutput, p);
8663                 }
8664
8665                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8666                     strcat(programStats.movelist, " ");
8667                     strcat(programStats.movelist, p);
8668                 }
8669
8670                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8671                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8672                     DisplayMove(currentMove - 1);
8673                 }
8674                 return;
8675             }
8676         }
8677         else {
8678             buf1[0] = NULLCHAR;
8679
8680             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8681                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8682             {
8683                 ChessProgramStats cpstats;
8684
8685                 if (plyext != ' ' && plyext != '\t') {
8686                     time *= 100;
8687                 }
8688
8689                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8690                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8691                     curscore = -curscore;
8692                 }
8693
8694                 cpstats.depth = plylev;
8695                 cpstats.nodes = nodes;
8696                 cpstats.time = time;
8697                 cpstats.score = curscore;
8698                 cpstats.got_only_move = 0;
8699                 cpstats.movelist[0] = '\0';
8700
8701                 if (buf1[0] != NULLCHAR) {
8702                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8703                 }
8704
8705                 cpstats.ok_to_send = 0;
8706                 cpstats.line_is_book = 0;
8707                 cpstats.nr_moves = 0;
8708                 cpstats.moves_left = 0;
8709
8710                 SendProgramStatsToFrontend( cps, &cpstats );
8711             }
8712         }
8713     }
8714 }
8715
8716
8717 /* Parse a game score from the character string "game", and
8718    record it as the history of the current game.  The game
8719    score is NOT assumed to start from the standard position.
8720    The display is not updated in any way.
8721    */
8722 void
8723 ParseGameHistory(game)
8724      char *game;
8725 {
8726     ChessMove moveType;
8727     int fromX, fromY, toX, toY, boardIndex;
8728     char promoChar;
8729     char *p, *q;
8730     char buf[MSG_SIZ];
8731
8732     if (appData.debugMode)
8733       fprintf(debugFP, "Parsing game history: %s\n", game);
8734
8735     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8736     gameInfo.site = StrSave(appData.icsHost);
8737     gameInfo.date = PGNDate();
8738     gameInfo.round = StrSave("-");
8739
8740     /* Parse out names of players */
8741     while (*game == ' ') game++;
8742     p = buf;
8743     while (*game != ' ') *p++ = *game++;
8744     *p = NULLCHAR;
8745     gameInfo.white = StrSave(buf);
8746     while (*game == ' ') game++;
8747     p = buf;
8748     while (*game != ' ' && *game != '\n') *p++ = *game++;
8749     *p = NULLCHAR;
8750     gameInfo.black = StrSave(buf);
8751
8752     /* Parse moves */
8753     boardIndex = blackPlaysFirst ? 1 : 0;
8754     yynewstr(game);
8755     for (;;) {
8756         yyboardindex = boardIndex;
8757         moveType = (ChessMove) Myylex();
8758         switch (moveType) {
8759           case IllegalMove:             /* maybe suicide chess, etc. */
8760   if (appData.debugMode) {
8761     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8762     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8763     setbuf(debugFP, NULL);
8764   }
8765           case WhitePromotion:
8766           case BlackPromotion:
8767           case WhiteNonPromotion:
8768           case BlackNonPromotion:
8769           case NormalMove:
8770           case WhiteCapturesEnPassant:
8771           case BlackCapturesEnPassant:
8772           case WhiteKingSideCastle:
8773           case WhiteQueenSideCastle:
8774           case BlackKingSideCastle:
8775           case BlackQueenSideCastle:
8776           case WhiteKingSideCastleWild:
8777           case WhiteQueenSideCastleWild:
8778           case BlackKingSideCastleWild:
8779           case BlackQueenSideCastleWild:
8780           /* PUSH Fabien */
8781           case WhiteHSideCastleFR:
8782           case WhiteASideCastleFR:
8783           case BlackHSideCastleFR:
8784           case BlackASideCastleFR:
8785           /* POP Fabien */
8786             fromX = currentMoveString[0] - AAA;
8787             fromY = currentMoveString[1] - ONE;
8788             toX = currentMoveString[2] - AAA;
8789             toY = currentMoveString[3] - ONE;
8790             promoChar = currentMoveString[4];
8791             break;
8792           case WhiteDrop:
8793           case BlackDrop:
8794             fromX = moveType == WhiteDrop ?
8795               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8796             (int) CharToPiece(ToLower(currentMoveString[0]));
8797             fromY = DROP_RANK;
8798             toX = currentMoveString[2] - AAA;
8799             toY = currentMoveString[3] - ONE;
8800             promoChar = NULLCHAR;
8801             break;
8802           case AmbiguousMove:
8803             /* bug? */
8804             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8805   if (appData.debugMode) {
8806     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8807     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8808     setbuf(debugFP, NULL);
8809   }
8810             DisplayError(buf, 0);
8811             return;
8812           case ImpossibleMove:
8813             /* bug? */
8814             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8815   if (appData.debugMode) {
8816     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8817     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8818     setbuf(debugFP, NULL);
8819   }
8820             DisplayError(buf, 0);
8821             return;
8822           case EndOfFile:
8823             if (boardIndex < backwardMostMove) {
8824                 /* Oops, gap.  How did that happen? */
8825                 DisplayError(_("Gap in move list"), 0);
8826                 return;
8827             }
8828             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8829             if (boardIndex > forwardMostMove) {
8830                 forwardMostMove = boardIndex;
8831             }
8832             return;
8833           case ElapsedTime:
8834             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8835                 strcat(parseList[boardIndex-1], " ");
8836                 strcat(parseList[boardIndex-1], yy_text);
8837             }
8838             continue;
8839           case Comment:
8840           case PGNTag:
8841           case NAG:
8842           default:
8843             /* ignore */
8844             continue;
8845           case WhiteWins:
8846           case BlackWins:
8847           case GameIsDrawn:
8848           case GameUnfinished:
8849             if (gameMode == IcsExamining) {
8850                 if (boardIndex < backwardMostMove) {
8851                     /* Oops, gap.  How did that happen? */
8852                     return;
8853                 }
8854                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8855                 return;
8856             }
8857             gameInfo.result = moveType;
8858             p = strchr(yy_text, '{');
8859             if (p == NULL) p = strchr(yy_text, '(');
8860             if (p == NULL) {
8861                 p = yy_text;
8862                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8863             } else {
8864                 q = strchr(p, *p == '{' ? '}' : ')');
8865                 if (q != NULL) *q = NULLCHAR;
8866                 p++;
8867             }
8868             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8869             gameInfo.resultDetails = StrSave(p);
8870             continue;
8871         }
8872         if (boardIndex >= forwardMostMove &&
8873             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8874             backwardMostMove = blackPlaysFirst ? 1 : 0;
8875             return;
8876         }
8877         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8878                                  fromY, fromX, toY, toX, promoChar,
8879                                  parseList[boardIndex]);
8880         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8881         /* currentMoveString is set as a side-effect of yylex */
8882         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8883         strcat(moveList[boardIndex], "\n");
8884         boardIndex++;
8885         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8886         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8887           case MT_NONE:
8888           case MT_STALEMATE:
8889           default:
8890             break;
8891           case MT_CHECK:
8892             if(gameInfo.variant != VariantShogi)
8893                 strcat(parseList[boardIndex - 1], "+");
8894             break;
8895           case MT_CHECKMATE:
8896           case MT_STAINMATE:
8897             strcat(parseList[boardIndex - 1], "#");
8898             break;
8899         }
8900     }
8901 }
8902
8903
8904 /* Apply a move to the given board  */
8905 void
8906 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8907      int fromX, fromY, toX, toY;
8908      int promoChar;
8909      Board board;
8910 {
8911   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8912   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8913
8914     /* [HGM] compute & store e.p. status and castling rights for new position */
8915     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8916
8917       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8918       oldEP = (signed char)board[EP_STATUS];
8919       board[EP_STATUS] = EP_NONE;
8920
8921       if( board[toY][toX] != EmptySquare )
8922            board[EP_STATUS] = EP_CAPTURE;
8923
8924   if (fromY == DROP_RANK) {
8925         /* must be first */
8926         piece = board[toY][toX] = (ChessSquare) fromX;
8927   } else {
8928       int i;
8929
8930       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8931            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8932                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8933       } else
8934       if( board[fromY][fromX] == WhitePawn ) {
8935            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8936                board[EP_STATUS] = EP_PAWN_MOVE;
8937            if( toY-fromY==2) {
8938                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8939                         gameInfo.variant != VariantBerolina || toX < fromX)
8940                       board[EP_STATUS] = toX | berolina;
8941                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8942                         gameInfo.variant != VariantBerolina || toX > fromX)
8943                       board[EP_STATUS] = toX;
8944            }
8945       } else
8946       if( board[fromY][fromX] == BlackPawn ) {
8947            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8948                board[EP_STATUS] = EP_PAWN_MOVE;
8949            if( toY-fromY== -2) {
8950                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8951                         gameInfo.variant != VariantBerolina || toX < fromX)
8952                       board[EP_STATUS] = toX | berolina;
8953                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8954                         gameInfo.variant != VariantBerolina || toX > fromX)
8955                       board[EP_STATUS] = toX;
8956            }
8957        }
8958
8959        for(i=0; i<nrCastlingRights; i++) {
8960            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8961               board[CASTLING][i] == toX   && castlingRank[i] == toY
8962              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8963        }
8964
8965      if (fromX == toX && fromY == toY) return;
8966
8967      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8968      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8969      if(gameInfo.variant == VariantKnightmate)
8970          king += (int) WhiteUnicorn - (int) WhiteKing;
8971
8972     /* Code added by Tord: */
8973     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8974     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8975         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8976       board[fromY][fromX] = EmptySquare;
8977       board[toY][toX] = EmptySquare;
8978       if((toX > fromX) != (piece == WhiteRook)) {
8979         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8980       } else {
8981         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8982       }
8983     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8984                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8985       board[fromY][fromX] = EmptySquare;
8986       board[toY][toX] = EmptySquare;
8987       if((toX > fromX) != (piece == BlackRook)) {
8988         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8989       } else {
8990         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8991       }
8992     /* End of code added by Tord */
8993
8994     } else if (board[fromY][fromX] == king
8995         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8996         && toY == fromY && toX > fromX+1) {
8997         board[fromY][fromX] = EmptySquare;
8998         board[toY][toX] = king;
8999         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9000         board[fromY][BOARD_RGHT-1] = EmptySquare;
9001     } else if (board[fromY][fromX] == king
9002         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9003                && toY == fromY && toX < fromX-1) {
9004         board[fromY][fromX] = EmptySquare;
9005         board[toY][toX] = king;
9006         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9007         board[fromY][BOARD_LEFT] = EmptySquare;
9008     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9009                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9010                && toY >= BOARD_HEIGHT-promoRank
9011                ) {
9012         /* white pawn promotion */
9013         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9014         if (board[toY][toX] == EmptySquare) {
9015             board[toY][toX] = WhiteQueen;
9016         }
9017         if(gameInfo.variant==VariantBughouse ||
9018            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9019             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9020         board[fromY][fromX] = EmptySquare;
9021     } else if ((fromY == BOARD_HEIGHT-4)
9022                && (toX != fromX)
9023                && gameInfo.variant != VariantXiangqi
9024                && gameInfo.variant != VariantBerolina
9025                && (board[fromY][fromX] == WhitePawn)
9026                && (board[toY][toX] == EmptySquare)) {
9027         board[fromY][fromX] = EmptySquare;
9028         board[toY][toX] = WhitePawn;
9029         captured = board[toY - 1][toX];
9030         board[toY - 1][toX] = EmptySquare;
9031     } else if ((fromY == BOARD_HEIGHT-4)
9032                && (toX == fromX)
9033                && gameInfo.variant == VariantBerolina
9034                && (board[fromY][fromX] == WhitePawn)
9035                && (board[toY][toX] == EmptySquare)) {
9036         board[fromY][fromX] = EmptySquare;
9037         board[toY][toX] = WhitePawn;
9038         if(oldEP & EP_BEROLIN_A) {
9039                 captured = board[fromY][fromX-1];
9040                 board[fromY][fromX-1] = EmptySquare;
9041         }else{  captured = board[fromY][fromX+1];
9042                 board[fromY][fromX+1] = EmptySquare;
9043         }
9044     } else if (board[fromY][fromX] == king
9045         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9046                && toY == fromY && toX > fromX+1) {
9047         board[fromY][fromX] = EmptySquare;
9048         board[toY][toX] = king;
9049         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9050         board[fromY][BOARD_RGHT-1] = EmptySquare;
9051     } else if (board[fromY][fromX] == king
9052         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9053                && toY == fromY && toX < fromX-1) {
9054         board[fromY][fromX] = EmptySquare;
9055         board[toY][toX] = king;
9056         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9057         board[fromY][BOARD_LEFT] = EmptySquare;
9058     } else if (fromY == 7 && fromX == 3
9059                && board[fromY][fromX] == BlackKing
9060                && toY == 7 && toX == 5) {
9061         board[fromY][fromX] = EmptySquare;
9062         board[toY][toX] = BlackKing;
9063         board[fromY][7] = EmptySquare;
9064         board[toY][4] = BlackRook;
9065     } else if (fromY == 7 && fromX == 3
9066                && board[fromY][fromX] == BlackKing
9067                && toY == 7 && toX == 1) {
9068         board[fromY][fromX] = EmptySquare;
9069         board[toY][toX] = BlackKing;
9070         board[fromY][0] = EmptySquare;
9071         board[toY][2] = BlackRook;
9072     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9073                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9074                && toY < promoRank
9075                ) {
9076         /* black pawn promotion */
9077         board[toY][toX] = CharToPiece(ToLower(promoChar));
9078         if (board[toY][toX] == EmptySquare) {
9079             board[toY][toX] = BlackQueen;
9080         }
9081         if(gameInfo.variant==VariantBughouse ||
9082            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9083             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9084         board[fromY][fromX] = EmptySquare;
9085     } else if ((fromY == 3)
9086                && (toX != fromX)
9087                && gameInfo.variant != VariantXiangqi
9088                && gameInfo.variant != VariantBerolina
9089                && (board[fromY][fromX] == BlackPawn)
9090                && (board[toY][toX] == EmptySquare)) {
9091         board[fromY][fromX] = EmptySquare;
9092         board[toY][toX] = BlackPawn;
9093         captured = board[toY + 1][toX];
9094         board[toY + 1][toX] = EmptySquare;
9095     } else if ((fromY == 3)
9096                && (toX == fromX)
9097                && gameInfo.variant == VariantBerolina
9098                && (board[fromY][fromX] == BlackPawn)
9099                && (board[toY][toX] == EmptySquare)) {
9100         board[fromY][fromX] = EmptySquare;
9101         board[toY][toX] = BlackPawn;
9102         if(oldEP & EP_BEROLIN_A) {
9103                 captured = board[fromY][fromX-1];
9104                 board[fromY][fromX-1] = EmptySquare;
9105         }else{  captured = board[fromY][fromX+1];
9106                 board[fromY][fromX+1] = EmptySquare;
9107         }
9108     } else {
9109         board[toY][toX] = board[fromY][fromX];
9110         board[fromY][fromX] = EmptySquare;
9111     }
9112   }
9113
9114     if (gameInfo.holdingsWidth != 0) {
9115
9116       /* !!A lot more code needs to be written to support holdings  */
9117       /* [HGM] OK, so I have written it. Holdings are stored in the */
9118       /* penultimate board files, so they are automaticlly stored   */
9119       /* in the game history.                                       */
9120       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9121                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9122         /* Delete from holdings, by decreasing count */
9123         /* and erasing image if necessary            */
9124         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9125         if(p < (int) BlackPawn) { /* white drop */
9126              p -= (int)WhitePawn;
9127                  p = PieceToNumber((ChessSquare)p);
9128              if(p >= gameInfo.holdingsSize) p = 0;
9129              if(--board[p][BOARD_WIDTH-2] <= 0)
9130                   board[p][BOARD_WIDTH-1] = EmptySquare;
9131              if((int)board[p][BOARD_WIDTH-2] < 0)
9132                         board[p][BOARD_WIDTH-2] = 0;
9133         } else {                  /* black drop */
9134              p -= (int)BlackPawn;
9135                  p = PieceToNumber((ChessSquare)p);
9136              if(p >= gameInfo.holdingsSize) p = 0;
9137              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9138                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9139              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9140                         board[BOARD_HEIGHT-1-p][1] = 0;
9141         }
9142       }
9143       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9144           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9145         /* [HGM] holdings: Add to holdings, if holdings exist */
9146         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9147                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9148                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9149         }
9150         p = (int) captured;
9151         if (p >= (int) BlackPawn) {
9152           p -= (int)BlackPawn;
9153           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9154                   /* in Shogi restore piece to its original  first */
9155                   captured = (ChessSquare) (DEMOTED captured);
9156                   p = DEMOTED p;
9157           }
9158           p = PieceToNumber((ChessSquare)p);
9159           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9160           board[p][BOARD_WIDTH-2]++;
9161           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9162         } else {
9163           p -= (int)WhitePawn;
9164           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9165                   captured = (ChessSquare) (DEMOTED captured);
9166                   p = DEMOTED p;
9167           }
9168           p = PieceToNumber((ChessSquare)p);
9169           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9170           board[BOARD_HEIGHT-1-p][1]++;
9171           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9172         }
9173       }
9174     } else if (gameInfo.variant == VariantAtomic) {
9175       if (captured != EmptySquare) {
9176         int y, x;
9177         for (y = toY-1; y <= toY+1; y++) {
9178           for (x = toX-1; x <= toX+1; x++) {
9179             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9180                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9181               board[y][x] = EmptySquare;
9182             }
9183           }
9184         }
9185         board[toY][toX] = EmptySquare;
9186       }
9187     }
9188     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9189         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9190     } else
9191     if(promoChar == '+') {
9192         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9193         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9194     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9195         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9196     }
9197     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9198                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9199         // [HGM] superchess: take promotion piece out of holdings
9200         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9201         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9202             if(!--board[k][BOARD_WIDTH-2])
9203                 board[k][BOARD_WIDTH-1] = EmptySquare;
9204         } else {
9205             if(!--board[BOARD_HEIGHT-1-k][1])
9206                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9207         }
9208     }
9209
9210 }
9211
9212 /* Updates forwardMostMove */
9213 void
9214 MakeMove(fromX, fromY, toX, toY, promoChar)
9215      int fromX, fromY, toX, toY;
9216      int promoChar;
9217 {
9218 //    forwardMostMove++; // [HGM] bare: moved downstream
9219
9220     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9221         int timeLeft; static int lastLoadFlag=0; int king, piece;
9222         piece = boards[forwardMostMove][fromY][fromX];
9223         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9224         if(gameInfo.variant == VariantKnightmate)
9225             king += (int) WhiteUnicorn - (int) WhiteKing;
9226         if(forwardMostMove == 0) {
9227             if(blackPlaysFirst)
9228                 fprintf(serverMoves, "%s;", second.tidy);
9229             fprintf(serverMoves, "%s;", first.tidy);
9230             if(!blackPlaysFirst)
9231                 fprintf(serverMoves, "%s;", second.tidy);
9232         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9233         lastLoadFlag = loadFlag;
9234         // print base move
9235         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9236         // print castling suffix
9237         if( toY == fromY && piece == king ) {
9238             if(toX-fromX > 1)
9239                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9240             if(fromX-toX >1)
9241                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9242         }
9243         // e.p. suffix
9244         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9245              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9246              boards[forwardMostMove][toY][toX] == EmptySquare
9247              && fromX != toX && fromY != toY)
9248                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9249         // promotion suffix
9250         if(promoChar != NULLCHAR)
9251                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9252         if(!loadFlag) {
9253             fprintf(serverMoves, "/%d/%d",
9254                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9255             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9256             else                      timeLeft = blackTimeRemaining/1000;
9257             fprintf(serverMoves, "/%d", timeLeft);
9258         }
9259         fflush(serverMoves);
9260     }
9261
9262     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9263       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9264                         0, 1);
9265       return;
9266     }
9267     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9268     if (commentList[forwardMostMove+1] != NULL) {
9269         free(commentList[forwardMostMove+1]);
9270         commentList[forwardMostMove+1] = NULL;
9271     }
9272     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9273     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9274     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9275     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9276     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9277     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9278     gameInfo.result = GameUnfinished;
9279     if (gameInfo.resultDetails != NULL) {
9280         free(gameInfo.resultDetails);
9281         gameInfo.resultDetails = NULL;
9282     }
9283     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9284                               moveList[forwardMostMove - 1]);
9285     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9286                              PosFlags(forwardMostMove - 1),
9287                              fromY, fromX, toY, toX, promoChar,
9288                              parseList[forwardMostMove - 1]);
9289     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9290       case MT_NONE:
9291       case MT_STALEMATE:
9292       default:
9293         break;
9294       case MT_CHECK:
9295         if(gameInfo.variant != VariantShogi)
9296             strcat(parseList[forwardMostMove - 1], "+");
9297         break;
9298       case MT_CHECKMATE:
9299       case MT_STAINMATE:
9300         strcat(parseList[forwardMostMove - 1], "#");
9301         break;
9302     }
9303     if (appData.debugMode) {
9304         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9305     }
9306
9307 }
9308
9309 /* Updates currentMove if not pausing */
9310 void
9311 ShowMove(fromX, fromY, toX, toY)
9312 {
9313     int instant = (gameMode == PlayFromGameFile) ?
9314         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9315     if(appData.noGUI) return;
9316     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9317         if (!instant) {
9318             if (forwardMostMove == currentMove + 1) {
9319                 AnimateMove(boards[forwardMostMove - 1],
9320                             fromX, fromY, toX, toY);
9321             }
9322             if (appData.highlightLastMove) {
9323                 SetHighlights(fromX, fromY, toX, toY);
9324             }
9325         }
9326         currentMove = forwardMostMove;
9327     }
9328
9329     if (instant) return;
9330
9331     DisplayMove(currentMove - 1);
9332     DrawPosition(FALSE, boards[currentMove]);
9333     DisplayBothClocks();
9334     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9335     DisplayBook(currentMove);
9336 }
9337
9338 void SendEgtPath(ChessProgramState *cps)
9339 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9340         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9341
9342         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9343
9344         while(*p) {
9345             char c, *q = name+1, *r, *s;
9346
9347             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9348             while(*p && *p != ',') *q++ = *p++;
9349             *q++ = ':'; *q = 0;
9350             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9351                 strcmp(name, ",nalimov:") == 0 ) {
9352                 // take nalimov path from the menu-changeable option first, if it is defined
9353               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9354                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9355             } else
9356             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9357                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9358                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9359                 s = r = StrStr(s, ":") + 1; // beginning of path info
9360                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9361                 c = *r; *r = 0;             // temporarily null-terminate path info
9362                     *--q = 0;               // strip of trailig ':' from name
9363                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9364                 *r = c;
9365                 SendToProgram(buf,cps);     // send egtbpath command for this format
9366             }
9367             if(*p == ',') p++; // read away comma to position for next format name
9368         }
9369 }
9370
9371 void
9372 InitChessProgram(cps, setup)
9373      ChessProgramState *cps;
9374      int setup; /* [HGM] needed to setup FRC opening position */
9375 {
9376     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9377     if (appData.noChessProgram) return;
9378     hintRequested = FALSE;
9379     bookRequested = FALSE;
9380
9381     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9382     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9383     if(cps->memSize) { /* [HGM] memory */
9384       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9385         SendToProgram(buf, cps);
9386     }
9387     SendEgtPath(cps); /* [HGM] EGT */
9388     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9389       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9390         SendToProgram(buf, cps);
9391     }
9392
9393     SendToProgram(cps->initString, cps);
9394     if (gameInfo.variant != VariantNormal &&
9395         gameInfo.variant != VariantLoadable
9396         /* [HGM] also send variant if board size non-standard */
9397         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9398                                             ) {
9399       char *v = VariantName(gameInfo.variant);
9400       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9401         /* [HGM] in protocol 1 we have to assume all variants valid */
9402         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9403         DisplayFatalError(buf, 0, 1);
9404         return;
9405       }
9406
9407       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9408       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9409       if( gameInfo.variant == VariantXiangqi )
9410            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9411       if( gameInfo.variant == VariantShogi )
9412            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9413       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9414            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9415       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9416           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9417            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9418       if( gameInfo.variant == VariantCourier )
9419            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9420       if( gameInfo.variant == VariantSuper )
9421            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9422       if( gameInfo.variant == VariantGreat )
9423            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9424       if( gameInfo.variant == VariantSChess )
9425            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9426
9427       if(overruled) {
9428         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9429                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9430            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9431            if(StrStr(cps->variants, b) == NULL) {
9432                // specific sized variant not known, check if general sizing allowed
9433                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9434                    if(StrStr(cps->variants, "boardsize") == NULL) {
9435                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9436                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9437                        DisplayFatalError(buf, 0, 1);
9438                        return;
9439                    }
9440                    /* [HGM] here we really should compare with the maximum supported board size */
9441                }
9442            }
9443       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9444       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9445       SendToProgram(buf, cps);
9446     }
9447     currentlyInitializedVariant = gameInfo.variant;
9448
9449     /* [HGM] send opening position in FRC to first engine */
9450     if(setup) {
9451           SendToProgram("force\n", cps);
9452           SendBoard(cps, 0);
9453           /* engine is now in force mode! Set flag to wake it up after first move. */
9454           setboardSpoiledMachineBlack = 1;
9455     }
9456
9457     if (cps->sendICS) {
9458       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9459       SendToProgram(buf, cps);
9460     }
9461     cps->maybeThinking = FALSE;
9462     cps->offeredDraw = 0;
9463     if (!appData.icsActive) {
9464         SendTimeControl(cps, movesPerSession, timeControl,
9465                         timeIncrement, appData.searchDepth,
9466                         searchTime);
9467     }
9468     if (appData.showThinking
9469         // [HGM] thinking: four options require thinking output to be sent
9470         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9471                                 ) {
9472         SendToProgram("post\n", cps);
9473     }
9474     SendToProgram("hard\n", cps);
9475     if (!appData.ponderNextMove) {
9476         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9477            it without being sure what state we are in first.  "hard"
9478            is not a toggle, so that one is OK.
9479          */
9480         SendToProgram("easy\n", cps);
9481     }
9482     if (cps->usePing) {
9483       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9484       SendToProgram(buf, cps);
9485     }
9486     cps->initDone = TRUE;
9487 }
9488
9489
9490 void
9491 StartChessProgram(cps)
9492      ChessProgramState *cps;
9493 {
9494     char buf[MSG_SIZ];
9495     int err;
9496
9497     if (appData.noChessProgram) return;
9498     cps->initDone = FALSE;
9499
9500     if (strcmp(cps->host, "localhost") == 0) {
9501         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9502     } else if (*appData.remoteShell == NULLCHAR) {
9503         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9504     } else {
9505         if (*appData.remoteUser == NULLCHAR) {
9506           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9507                     cps->program);
9508         } else {
9509           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9510                     cps->host, appData.remoteUser, cps->program);
9511         }
9512         err = StartChildProcess(buf, "", &cps->pr);
9513     }
9514
9515     if (err != 0) {
9516       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9517         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9518         if(cps != &first) return;
9519         appData.noChessProgram = TRUE;
9520         ThawUI();
9521         SetNCPMode();
9522 //      DisplayFatalError(buf, err, 1);
9523 //      cps->pr = NoProc;
9524 //      cps->isr = NULL;
9525         return;
9526     }
9527
9528     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9529     if (cps->protocolVersion > 1) {
9530       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9531       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9532       cps->comboCnt = 0;  //                and values of combo boxes
9533       SendToProgram(buf, cps);
9534     } else {
9535       SendToProgram("xboard\n", cps);
9536     }
9537 }
9538
9539 void
9540 TwoMachinesEventIfReady P((void))
9541 {
9542   static int curMess = 0;
9543   if (first.lastPing != first.lastPong) {
9544     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9545     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9546     return;
9547   }
9548   if (second.lastPing != second.lastPong) {
9549     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9550     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9551     return;
9552   }
9553   DisplayMessage("", ""); curMess = 0;
9554   ThawUI();
9555   TwoMachinesEvent();
9556 }
9557
9558 char *
9559 MakeName(char *template)
9560 {
9561     time_t clock;
9562     struct tm *tm;
9563     static char buf[MSG_SIZ];
9564     char *p = buf;
9565     int i;
9566
9567     clock = time((time_t *)NULL);
9568     tm = localtime(&clock);
9569
9570     while(*p++ = *template++) if(p[-1] == '%') {
9571         switch(*template++) {
9572           case 0:   *p = 0; return buf;
9573           case 'Y': i = tm->tm_year+1900; break;
9574           case 'y': i = tm->tm_year-100; break;
9575           case 'M': i = tm->tm_mon+1; break;
9576           case 'd': i = tm->tm_mday; break;
9577           case 'h': i = tm->tm_hour; break;
9578           case 'm': i = tm->tm_min; break;
9579           case 's': i = tm->tm_sec; break;
9580           default:  i = 0;
9581         }
9582         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9583     }
9584     return buf;
9585 }
9586
9587 int
9588 CountPlayers(char *p)
9589 {
9590     int n = 0;
9591     while(p = strchr(p, '\n')) p++, n++; // count participants
9592     return n;
9593 }
9594
9595 FILE *
9596 WriteTourneyFile(char *results)
9597 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9598     FILE *f = fopen(appData.tourneyFile, "w");
9599     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9600         // create a file with tournament description
9601         fprintf(f, "-participants {%s}\n", appData.participants);
9602         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9603         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9604         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9605         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9606         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9607         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9608         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9609         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9610         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9611         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9612         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9613         if(searchTime > 0)
9614                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9615         else {
9616                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9617                 fprintf(f, "-tc %s\n", appData.timeControl);
9618                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9619         }
9620         fprintf(f, "-results \"%s\"\n", results);
9621     }
9622     return f;
9623 }
9624
9625 int
9626 CreateTourney(char *name)
9627 {
9628         FILE *f;
9629         if(name[0] == NULLCHAR) {
9630             if(appData.participants[0])
9631                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9632             return 0;
9633         }
9634         f = fopen(name, "r");
9635         if(f) { // file exists
9636             ASSIGN(appData.tourneyFile, name);
9637             ParseArgsFromFile(f); // parse it
9638         } else {
9639             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9640             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9641                 DisplayError(_("Not enough participants"), 0);
9642                 return 0;
9643             }
9644             ASSIGN(appData.tourneyFile, name);
9645             if((f = WriteTourneyFile("")) == NULL) return 0;
9646         }
9647         fclose(f);
9648         appData.noChessProgram = FALSE;
9649         appData.clockMode = TRUE;
9650         SetGNUMode();
9651         return 1;
9652 }
9653
9654 #define MAXENGINES 1000
9655 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9656
9657 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9658 {
9659     char buf[MSG_SIZ], *p, *q;
9660     int i=1;
9661     while(*names) {
9662         p = names; q = buf;
9663         while(*p && *p != '\n') *q++ = *p++;
9664         *q = 0;
9665         if(engineList[i]) free(engineList[i]);
9666         engineList[i] = strdup(buf);
9667         if(*p == '\n') p++;
9668         TidyProgramName(engineList[i], "localhost", buf);
9669         if(engineMnemonic[i]) free(engineMnemonic[i]);
9670         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9671             strcat(buf, " (");
9672             sscanf(q + 8, "%s", buf + strlen(buf));
9673             strcat(buf, ")");
9674         }
9675         engineMnemonic[i] = strdup(buf);
9676         names = p; i++;
9677       if(i > MAXENGINES - 2) break;
9678     }
9679     engineList[i] = NULL;
9680 }
9681
9682 // following implemented as macro to avoid type limitations
9683 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9684
9685 void SwapEngines(int n)
9686 {   // swap settings for first engine and other engine (so far only some selected options)
9687     int h;
9688     char *p;
9689     if(n == 0) return;
9690     SWAP(directory, p)
9691     SWAP(chessProgram, p)
9692     SWAP(isUCI, h)
9693     SWAP(hasOwnBookUCI, h)
9694     SWAP(protocolVersion, h)
9695     SWAP(reuse, h)
9696     SWAP(scoreIsAbsolute, h)
9697     SWAP(timeOdds, h)
9698     SWAP(logo, p)
9699     SWAP(pgnName, p)
9700 }
9701
9702 void
9703 SetPlayer(int player)
9704 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9705     int i;
9706     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9707     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9708     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9709     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9710     if(mnemonic[i]) {
9711         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9712         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9713         ParseArgsFromString(buf);
9714     }
9715     free(engineName);
9716 }
9717
9718 int
9719 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9720 {   // determine players from game number
9721     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9722
9723     if(appData.tourneyType == 0) {
9724         roundsPerCycle = (nPlayers - 1) | 1;
9725         pairingsPerRound = nPlayers / 2;
9726     } else if(appData.tourneyType > 0) {
9727         roundsPerCycle = nPlayers - appData.tourneyType;
9728         pairingsPerRound = appData.tourneyType;
9729     }
9730     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9731     gamesPerCycle = gamesPerRound * roundsPerCycle;
9732     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9733     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9734     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9735     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9736     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9737     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9738
9739     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9740     if(appData.roundSync) *syncInterval = gamesPerRound;
9741
9742     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9743
9744     if(appData.tourneyType == 0) {
9745         if(curPairing == (nPlayers-1)/2 ) {
9746             *whitePlayer = curRound;
9747             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9748         } else {
9749             *whitePlayer = curRound - pairingsPerRound + curPairing;
9750             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9751             *blackPlayer = curRound + pairingsPerRound - curPairing;
9752             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9753         }
9754     } else if(appData.tourneyType > 0) {
9755         *whitePlayer = curPairing;
9756         *blackPlayer = curRound + appData.tourneyType;
9757     }
9758
9759     // take care of white/black alternation per round. 
9760     // For cycles and games this is already taken care of by default, derived from matchGame!
9761     return curRound & 1;
9762 }
9763
9764 int
9765 NextTourneyGame(int nr, int *swapColors)
9766 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9767     char *p, *q;
9768     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9769     FILE *tf;
9770     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9771     tf = fopen(appData.tourneyFile, "r");
9772     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9773     ParseArgsFromFile(tf); fclose(tf);
9774     InitTimeControls(); // TC might be altered from tourney file
9775
9776     nPlayers = CountPlayers(appData.participants); // count participants
9777     if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9778         if(nr>=0 && !pairingReceived) {
9779             char buf[1<<16];
9780             if(pairing.pr == NoProc) StartChessProgram(&pairing);
9781             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9782             SendToProgram(buf, &pairing);
9783             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9784             SendToProgram(buf, &pairing);
9785             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9786         }
9787         pairingReceived = 0;                              // ... so we continue here 
9788         syncInterval = nPlayers/2; *swapColors = 0;
9789         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9790         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9791         matchGame = 1; roundNr = nr / syncInterval + 1;
9792     } 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(first.pr != NoProc) return 1; // engines already loaded
9808
9809     // redefine engines, engine dir, etc.
9810     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9811     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9812     SwapEngines(1);
9813     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9814     SwapEngines(1);         // and make that valid for second engine by swapping
9815     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9816     InitEngine(&second, 1);
9817     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9818     return 1;
9819 }
9820
9821 void
9822 NextMatchGame()
9823 {   // performs game initialization that does not invoke engines, and then tries to start the game
9824     int firstWhite, swapColors = 0;
9825     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9826     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9827     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9828     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9829     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9830     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9831     Reset(FALSE, first.pr != NoProc);
9832     appData.noChessProgram = FALSE;
9833     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9834     TwoMachinesEvent();
9835 }
9836
9837 void UserAdjudicationEvent( int result )
9838 {
9839     ChessMove gameResult = GameIsDrawn;
9840
9841     if( result > 0 ) {
9842         gameResult = WhiteWins;
9843     }
9844     else if( result < 0 ) {
9845         gameResult = BlackWins;
9846     }
9847
9848     if( gameMode == TwoMachinesPlay ) {
9849         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9850     }
9851 }
9852
9853
9854 // [HGM] save: calculate checksum of game to make games easily identifiable
9855 int StringCheckSum(char *s)
9856 {
9857         int i = 0;
9858         if(s==NULL) return 0;
9859         while(*s) i = i*259 + *s++;
9860         return i;
9861 }
9862
9863 int GameCheckSum()
9864 {
9865         int i, sum=0;
9866         for(i=backwardMostMove; i<forwardMostMove; i++) {
9867                 sum += pvInfoList[i].depth;
9868                 sum += StringCheckSum(parseList[i]);
9869                 sum += StringCheckSum(commentList[i]);
9870                 sum *= 261;
9871         }
9872         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9873         return sum + StringCheckSum(commentList[i]);
9874 } // end of save patch
9875
9876 void
9877 GameEnds(result, resultDetails, whosays)
9878      ChessMove result;
9879      char *resultDetails;
9880      int whosays;
9881 {
9882     GameMode nextGameMode;
9883     int isIcsGame;
9884     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9885
9886     if(endingGame) return; /* [HGM] crash: forbid recursion */
9887     endingGame = 1;
9888     if(twoBoards) { // [HGM] dual: switch back to one board
9889         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9890         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9891     }
9892     if (appData.debugMode) {
9893       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9894               result, resultDetails ? resultDetails : "(null)", whosays);
9895     }
9896
9897     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9898
9899     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9900         /* If we are playing on ICS, the server decides when the
9901            game is over, but the engine can offer to draw, claim
9902            a draw, or resign.
9903          */
9904 #if ZIPPY
9905         if (appData.zippyPlay && first.initDone) {
9906             if (result == GameIsDrawn) {
9907                 /* In case draw still needs to be claimed */
9908                 SendToICS(ics_prefix);
9909                 SendToICS("draw\n");
9910             } else if (StrCaseStr(resultDetails, "resign")) {
9911                 SendToICS(ics_prefix);
9912                 SendToICS("resign\n");
9913             }
9914         }
9915 #endif
9916         endingGame = 0; /* [HGM] crash */
9917         return;
9918     }
9919
9920     /* If we're loading the game from a file, stop */
9921     if (whosays == GE_FILE) {
9922       (void) StopLoadGameTimer();
9923       gameFileFP = NULL;
9924     }
9925
9926     /* Cancel draw offers */
9927     first.offeredDraw = second.offeredDraw = 0;
9928
9929     /* If this is an ICS game, only ICS can really say it's done;
9930        if not, anyone can. */
9931     isIcsGame = (gameMode == IcsPlayingWhite ||
9932                  gameMode == IcsPlayingBlack ||
9933                  gameMode == IcsObserving    ||
9934                  gameMode == IcsExamining);
9935
9936     if (!isIcsGame || whosays == GE_ICS) {
9937         /* OK -- not an ICS game, or ICS said it was done */
9938         StopClocks();
9939         if (!isIcsGame && !appData.noChessProgram)
9940           SetUserThinkingEnables();
9941
9942         /* [HGM] if a machine claims the game end we verify this claim */
9943         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9944             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9945                 char claimer;
9946                 ChessMove trueResult = (ChessMove) -1;
9947
9948                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9949                                             first.twoMachinesColor[0] :
9950                                             second.twoMachinesColor[0] ;
9951
9952                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9953                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9954                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9955                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9956                 } else
9957                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9958                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9959                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9960                 } else
9961                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9962                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9963                 }
9964
9965                 // now verify win claims, but not in drop games, as we don't understand those yet
9966                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9967                                                  || gameInfo.variant == VariantGreat) &&
9968                     (result == WhiteWins && claimer == 'w' ||
9969                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9970                       if (appData.debugMode) {
9971                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9972                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9973                       }
9974                       if(result != trueResult) {
9975                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9976                               result = claimer == 'w' ? BlackWins : WhiteWins;
9977                               resultDetails = buf;
9978                       }
9979                 } else
9980                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9981                     && (forwardMostMove <= backwardMostMove ||
9982                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9983                         (claimer=='b')==(forwardMostMove&1))
9984                                                                                   ) {
9985                       /* [HGM] verify: draws that were not flagged are false claims */
9986                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9987                       result = claimer == 'w' ? BlackWins : WhiteWins;
9988                       resultDetails = buf;
9989                 }
9990                 /* (Claiming a loss is accepted no questions asked!) */
9991             }
9992             /* [HGM] bare: don't allow bare King to win */
9993             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9994                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9995                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9996                && result != GameIsDrawn)
9997             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9998                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9999                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10000                         if(p >= 0 && p <= (int)WhiteKing) k++;
10001                 }
10002                 if (appData.debugMode) {
10003                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10004                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10005                 }
10006                 if(k <= 1) {
10007                         result = GameIsDrawn;
10008                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10009                         resultDetails = buf;
10010                 }
10011             }
10012         }
10013
10014
10015         if(serverMoves != NULL && !loadFlag) { char c = '=';
10016             if(result==WhiteWins) c = '+';
10017             if(result==BlackWins) c = '-';
10018             if(resultDetails != NULL)
10019                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10020         }
10021         if (resultDetails != NULL) {
10022             gameInfo.result = result;
10023             gameInfo.resultDetails = StrSave(resultDetails);
10024
10025             /* display last move only if game was not loaded from file */
10026             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10027                 DisplayMove(currentMove - 1);
10028
10029             if (forwardMostMove != 0) {
10030                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10031                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10032                                                                 ) {
10033                     if (*appData.saveGameFile != NULLCHAR) {
10034                         SaveGameToFile(appData.saveGameFile, TRUE);
10035                     } else if (appData.autoSaveGames) {
10036                         AutoSaveGame();
10037                     }
10038                     if (*appData.savePositionFile != NULLCHAR) {
10039                         SavePositionToFile(appData.savePositionFile);
10040                     }
10041                 }
10042             }
10043
10044             /* Tell program how game ended in case it is learning */
10045             /* [HGM] Moved this to after saving the PGN, just in case */
10046             /* engine died and we got here through time loss. In that */
10047             /* case we will get a fatal error writing the pipe, which */
10048             /* would otherwise lose us the PGN.                       */
10049             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10050             /* output during GameEnds should never be fatal anymore   */
10051             if (gameMode == MachinePlaysWhite ||
10052                 gameMode == MachinePlaysBlack ||
10053                 gameMode == TwoMachinesPlay ||
10054                 gameMode == IcsPlayingWhite ||
10055                 gameMode == IcsPlayingBlack ||
10056                 gameMode == BeginningOfGame) {
10057                 char buf[MSG_SIZ];
10058                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10059                         resultDetails);
10060                 if (first.pr != NoProc) {
10061                     SendToProgram(buf, &first);
10062                 }
10063                 if (second.pr != NoProc &&
10064                     gameMode == TwoMachinesPlay) {
10065                     SendToProgram(buf, &second);
10066                 }
10067             }
10068         }
10069
10070         if (appData.icsActive) {
10071             if (appData.quietPlay &&
10072                 (gameMode == IcsPlayingWhite ||
10073                  gameMode == IcsPlayingBlack)) {
10074                 SendToICS(ics_prefix);
10075                 SendToICS("set shout 1\n");
10076             }
10077             nextGameMode = IcsIdle;
10078             ics_user_moved = FALSE;
10079             /* clean up premove.  It's ugly when the game has ended and the
10080              * premove highlights are still on the board.
10081              */
10082             if (gotPremove) {
10083               gotPremove = FALSE;
10084               ClearPremoveHighlights();
10085               DrawPosition(FALSE, boards[currentMove]);
10086             }
10087             if (whosays == GE_ICS) {
10088                 switch (result) {
10089                 case WhiteWins:
10090                     if (gameMode == IcsPlayingWhite)
10091                         PlayIcsWinSound();
10092                     else if(gameMode == IcsPlayingBlack)
10093                         PlayIcsLossSound();
10094                     break;
10095                 case BlackWins:
10096                     if (gameMode == IcsPlayingBlack)
10097                         PlayIcsWinSound();
10098                     else if(gameMode == IcsPlayingWhite)
10099                         PlayIcsLossSound();
10100                     break;
10101                 case GameIsDrawn:
10102                     PlayIcsDrawSound();
10103                     break;
10104                 default:
10105                     PlayIcsUnfinishedSound();
10106                 }
10107             }
10108         } else if (gameMode == EditGame ||
10109                    gameMode == PlayFromGameFile ||
10110                    gameMode == AnalyzeMode ||
10111                    gameMode == AnalyzeFile) {
10112             nextGameMode = gameMode;
10113         } else {
10114             nextGameMode = EndOfGame;
10115         }
10116         pausing = FALSE;
10117         ModeHighlight();
10118     } else {
10119         nextGameMode = gameMode;
10120     }
10121
10122     if (appData.noChessProgram) {
10123         gameMode = nextGameMode;
10124         ModeHighlight();
10125         endingGame = 0; /* [HGM] crash */
10126         return;
10127     }
10128
10129     if (first.reuse) {
10130         /* Put first chess program into idle state */
10131         if (first.pr != NoProc &&
10132             (gameMode == MachinePlaysWhite ||
10133              gameMode == MachinePlaysBlack ||
10134              gameMode == TwoMachinesPlay ||
10135              gameMode == IcsPlayingWhite ||
10136              gameMode == IcsPlayingBlack ||
10137              gameMode == BeginningOfGame)) {
10138             SendToProgram("force\n", &first);
10139             if (first.usePing) {
10140               char buf[MSG_SIZ];
10141               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10142               SendToProgram(buf, &first);
10143             }
10144         }
10145     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10146         /* Kill off first chess program */
10147         if (first.isr != NULL)
10148           RemoveInputSource(first.isr);
10149         first.isr = NULL;
10150
10151         if (first.pr != NoProc) {
10152             ExitAnalyzeMode();
10153             DoSleep( appData.delayBeforeQuit );
10154             SendToProgram("quit\n", &first);
10155             DoSleep( appData.delayAfterQuit );
10156             DestroyChildProcess(first.pr, first.useSigterm);
10157         }
10158         first.pr = NoProc;
10159     }
10160     if (second.reuse) {
10161         /* Put second chess program into idle state */
10162         if (second.pr != NoProc &&
10163             gameMode == TwoMachinesPlay) {
10164             SendToProgram("force\n", &second);
10165             if (second.usePing) {
10166               char buf[MSG_SIZ];
10167               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10168               SendToProgram(buf, &second);
10169             }
10170         }
10171     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10172         /* Kill off second chess program */
10173         if (second.isr != NULL)
10174           RemoveInputSource(second.isr);
10175         second.isr = NULL;
10176
10177         if (second.pr != NoProc) {
10178             DoSleep( appData.delayBeforeQuit );
10179             SendToProgram("quit\n", &second);
10180             DoSleep( appData.delayAfterQuit );
10181             DestroyChildProcess(second.pr, second.useSigterm);
10182         }
10183         second.pr = NoProc;
10184     }
10185
10186     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10187         char resChar = '=';
10188         switch (result) {
10189         case WhiteWins:
10190           resChar = '+';
10191           if (first.twoMachinesColor[0] == 'w') {
10192             first.matchWins++;
10193           } else {
10194             second.matchWins++;
10195           }
10196           break;
10197         case BlackWins:
10198           resChar = '-';
10199           if (first.twoMachinesColor[0] == 'b') {
10200             first.matchWins++;
10201           } else {
10202             second.matchWins++;
10203           }
10204           break;
10205         case GameUnfinished:
10206           resChar = ' ';
10207         default:
10208           break;
10209         }
10210
10211         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10212         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10213             ReserveGame(nextGame, resChar); // sets nextGame
10214             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10215             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10216         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10217
10218         if (nextGame <= appData.matchGames && !abortMatch) {
10219             gameMode = nextGameMode;
10220             matchGame = nextGame; // this will be overruled in tourney mode!
10221             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10222             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10223             endingGame = 0; /* [HGM] crash */
10224             return;
10225         } else {
10226             gameMode = nextGameMode;
10227             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10228                      first.tidy, second.tidy,
10229                      first.matchWins, second.matchWins,
10230                      appData.matchGames - (first.matchWins + second.matchWins));
10231             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10232             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10233                 first.twoMachinesColor = "black\n";
10234                 second.twoMachinesColor = "white\n";
10235             } else {
10236                 first.twoMachinesColor = "white\n";
10237                 second.twoMachinesColor = "black\n";
10238             }
10239         }
10240     }
10241     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10242         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10243       ExitAnalyzeMode();
10244     gameMode = nextGameMode;
10245     ModeHighlight();
10246     endingGame = 0;  /* [HGM] crash */
10247     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10248         if(matchMode == TRUE) { // match through command line: exit with or without popup
10249             if(ranking) {
10250                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10251                 else ExitEvent(0);
10252             } else DisplayFatalError(buf, 0, 0);
10253         } else { // match through menu; just stop, with or without popup
10254             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10255             if(ranking){
10256                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10257             } else DisplayNote(buf);
10258       }
10259       if(ranking) free(ranking);
10260     }
10261 }
10262
10263 /* Assumes program was just initialized (initString sent).
10264    Leaves program in force mode. */
10265 void
10266 FeedMovesToProgram(cps, upto)
10267      ChessProgramState *cps;
10268      int upto;
10269 {
10270     int i;
10271
10272     if (appData.debugMode)
10273       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10274               startedFromSetupPosition ? "position and " : "",
10275               backwardMostMove, upto, cps->which);
10276     if(currentlyInitializedVariant != gameInfo.variant) {
10277       char buf[MSG_SIZ];
10278         // [HGM] variantswitch: make engine aware of new variant
10279         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10280                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10281         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10282         SendToProgram(buf, cps);
10283         currentlyInitializedVariant = gameInfo.variant;
10284     }
10285     SendToProgram("force\n", cps);
10286     if (startedFromSetupPosition) {
10287         SendBoard(cps, backwardMostMove);
10288     if (appData.debugMode) {
10289         fprintf(debugFP, "feedMoves\n");
10290     }
10291     }
10292     for (i = backwardMostMove; i < upto; i++) {
10293         SendMoveToProgram(i, cps);
10294     }
10295 }
10296
10297
10298 int
10299 ResurrectChessProgram()
10300 {
10301      /* The chess program may have exited.
10302         If so, restart it and feed it all the moves made so far. */
10303     static int doInit = 0;
10304
10305     if (appData.noChessProgram) return 1;
10306
10307     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10308         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10309         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10310         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10311     } else {
10312         if (first.pr != NoProc) return 1;
10313         StartChessProgram(&first);
10314     }
10315     InitChessProgram(&first, FALSE);
10316     FeedMovesToProgram(&first, currentMove);
10317
10318     if (!first.sendTime) {
10319         /* can't tell gnuchess what its clock should read,
10320            so we bow to its notion. */
10321         ResetClocks();
10322         timeRemaining[0][currentMove] = whiteTimeRemaining;
10323         timeRemaining[1][currentMove] = blackTimeRemaining;
10324     }
10325
10326     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10327                 appData.icsEngineAnalyze) && first.analysisSupport) {
10328       SendToProgram("analyze\n", &first);
10329       first.analyzing = TRUE;
10330     }
10331     return 1;
10332 }
10333
10334 /*
10335  * Button procedures
10336  */
10337 void
10338 Reset(redraw, init)
10339      int redraw, init;
10340 {
10341     int i;
10342
10343     if (appData.debugMode) {
10344         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10345                 redraw, init, gameMode);
10346     }
10347     CleanupTail(); // [HGM] vari: delete any stored variations
10348     pausing = pauseExamInvalid = FALSE;
10349     startedFromSetupPosition = blackPlaysFirst = FALSE;
10350     firstMove = TRUE;
10351     whiteFlag = blackFlag = FALSE;
10352     userOfferedDraw = FALSE;
10353     hintRequested = bookRequested = FALSE;
10354     first.maybeThinking = FALSE;
10355     second.maybeThinking = FALSE;
10356     first.bookSuspend = FALSE; // [HGM] book
10357     second.bookSuspend = FALSE;
10358     thinkOutput[0] = NULLCHAR;
10359     lastHint[0] = NULLCHAR;
10360     ClearGameInfo(&gameInfo);
10361     gameInfo.variant = StringToVariant(appData.variant);
10362     ics_user_moved = ics_clock_paused = FALSE;
10363     ics_getting_history = H_FALSE;
10364     ics_gamenum = -1;
10365     white_holding[0] = black_holding[0] = NULLCHAR;
10366     ClearProgramStats();
10367     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10368
10369     ResetFrontEnd();
10370     ClearHighlights();
10371     flipView = appData.flipView;
10372     ClearPremoveHighlights();
10373     gotPremove = FALSE;
10374     alarmSounded = FALSE;
10375
10376     GameEnds(EndOfFile, NULL, GE_PLAYER);
10377     if(appData.serverMovesName != NULL) {
10378         /* [HGM] prepare to make moves file for broadcasting */
10379         clock_t t = clock();
10380         if(serverMoves != NULL) fclose(serverMoves);
10381         serverMoves = fopen(appData.serverMovesName, "r");
10382         if(serverMoves != NULL) {
10383             fclose(serverMoves);
10384             /* delay 15 sec before overwriting, so all clients can see end */
10385             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10386         }
10387         serverMoves = fopen(appData.serverMovesName, "w");
10388     }
10389
10390     ExitAnalyzeMode();
10391     gameMode = BeginningOfGame;
10392     ModeHighlight();
10393     if(appData.icsActive) gameInfo.variant = VariantNormal;
10394     currentMove = forwardMostMove = backwardMostMove = 0;
10395     InitPosition(redraw);
10396     for (i = 0; i < MAX_MOVES; i++) {
10397         if (commentList[i] != NULL) {
10398             free(commentList[i]);
10399             commentList[i] = NULL;
10400         }
10401     }
10402     ResetClocks();
10403     timeRemaining[0][0] = whiteTimeRemaining;
10404     timeRemaining[1][0] = blackTimeRemaining;
10405
10406     if (first.pr == NULL) {
10407         StartChessProgram(&first);
10408     }
10409     if (init) {
10410             InitChessProgram(&first, startedFromSetupPosition);
10411     }
10412     DisplayTitle("");
10413     DisplayMessage("", "");
10414     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10415     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10416 }
10417
10418 void
10419 AutoPlayGameLoop()
10420 {
10421     for (;;) {
10422         if (!AutoPlayOneMove())
10423           return;
10424         if (matchMode || appData.timeDelay == 0)
10425           continue;
10426         if (appData.timeDelay < 0)
10427           return;
10428         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10429         break;
10430     }
10431 }
10432
10433
10434 int
10435 AutoPlayOneMove()
10436 {
10437     int fromX, fromY, toX, toY;
10438
10439     if (appData.debugMode) {
10440       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10441     }
10442
10443     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10444       return FALSE;
10445
10446     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10447       pvInfoList[currentMove].depth = programStats.depth;
10448       pvInfoList[currentMove].score = programStats.score;
10449       pvInfoList[currentMove].time  = 0;
10450       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10451     }
10452
10453     if (currentMove >= forwardMostMove) {
10454       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10455       gameMode = EditGame;
10456       ModeHighlight();
10457
10458       /* [AS] Clear current move marker at the end of a game */
10459       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10460
10461       return FALSE;
10462     }
10463
10464     toX = moveList[currentMove][2] - AAA;
10465     toY = moveList[currentMove][3] - ONE;
10466
10467     if (moveList[currentMove][1] == '@') {
10468         if (appData.highlightLastMove) {
10469             SetHighlights(-1, -1, toX, toY);
10470         }
10471     } else {
10472         fromX = moveList[currentMove][0] - AAA;
10473         fromY = moveList[currentMove][1] - ONE;
10474
10475         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10476
10477         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10478
10479         if (appData.highlightLastMove) {
10480             SetHighlights(fromX, fromY, toX, toY);
10481         }
10482     }
10483     DisplayMove(currentMove);
10484     SendMoveToProgram(currentMove++, &first);
10485     DisplayBothClocks();
10486     DrawPosition(FALSE, boards[currentMove]);
10487     // [HGM] PV info: always display, routine tests if empty
10488     DisplayComment(currentMove - 1, commentList[currentMove]);
10489     return TRUE;
10490 }
10491
10492
10493 int
10494 LoadGameOneMove(readAhead)
10495      ChessMove readAhead;
10496 {
10497     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10498     char promoChar = NULLCHAR;
10499     ChessMove moveType;
10500     char move[MSG_SIZ];
10501     char *p, *q;
10502
10503     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10504         gameMode != AnalyzeMode && gameMode != Training) {
10505         gameFileFP = NULL;
10506         return FALSE;
10507     }
10508
10509     yyboardindex = forwardMostMove;
10510     if (readAhead != EndOfFile) {
10511       moveType = readAhead;
10512     } else {
10513       if (gameFileFP == NULL)
10514           return FALSE;
10515       moveType = (ChessMove) Myylex();
10516     }
10517
10518     done = FALSE;
10519     switch (moveType) {
10520       case Comment:
10521         if (appData.debugMode)
10522           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10523         p = yy_text;
10524
10525         /* append the comment but don't display it */
10526         AppendComment(currentMove, p, FALSE);
10527         return TRUE;
10528
10529       case WhiteCapturesEnPassant:
10530       case BlackCapturesEnPassant:
10531       case WhitePromotion:
10532       case BlackPromotion:
10533       case WhiteNonPromotion:
10534       case BlackNonPromotion:
10535       case NormalMove:
10536       case WhiteKingSideCastle:
10537       case WhiteQueenSideCastle:
10538       case BlackKingSideCastle:
10539       case BlackQueenSideCastle:
10540       case WhiteKingSideCastleWild:
10541       case WhiteQueenSideCastleWild:
10542       case BlackKingSideCastleWild:
10543       case BlackQueenSideCastleWild:
10544       /* PUSH Fabien */
10545       case WhiteHSideCastleFR:
10546       case WhiteASideCastleFR:
10547       case BlackHSideCastleFR:
10548       case BlackASideCastleFR:
10549       /* POP Fabien */
10550         if (appData.debugMode)
10551           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10552         fromX = currentMoveString[0] - AAA;
10553         fromY = currentMoveString[1] - ONE;
10554         toX = currentMoveString[2] - AAA;
10555         toY = currentMoveString[3] - ONE;
10556         promoChar = currentMoveString[4];
10557         break;
10558
10559       case WhiteDrop:
10560       case BlackDrop:
10561         if (appData.debugMode)
10562           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10563         fromX = moveType == WhiteDrop ?
10564           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10565         (int) CharToPiece(ToLower(currentMoveString[0]));
10566         fromY = DROP_RANK;
10567         toX = currentMoveString[2] - AAA;
10568         toY = currentMoveString[3] - ONE;
10569         break;
10570
10571       case WhiteWins:
10572       case BlackWins:
10573       case GameIsDrawn:
10574       case GameUnfinished:
10575         if (appData.debugMode)
10576           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10577         p = strchr(yy_text, '{');
10578         if (p == NULL) p = strchr(yy_text, '(');
10579         if (p == NULL) {
10580             p = yy_text;
10581             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10582         } else {
10583             q = strchr(p, *p == '{' ? '}' : ')');
10584             if (q != NULL) *q = NULLCHAR;
10585             p++;
10586         }
10587         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10588         GameEnds(moveType, p, GE_FILE);
10589         done = TRUE;
10590         if (cmailMsgLoaded) {
10591             ClearHighlights();
10592             flipView = WhiteOnMove(currentMove);
10593             if (moveType == GameUnfinished) flipView = !flipView;
10594             if (appData.debugMode)
10595               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10596         }
10597         break;
10598
10599       case EndOfFile:
10600         if (appData.debugMode)
10601           fprintf(debugFP, "Parser hit end of file\n");
10602         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10603           case MT_NONE:
10604           case MT_CHECK:
10605             break;
10606           case MT_CHECKMATE:
10607           case MT_STAINMATE:
10608             if (WhiteOnMove(currentMove)) {
10609                 GameEnds(BlackWins, "Black mates", GE_FILE);
10610             } else {
10611                 GameEnds(WhiteWins, "White mates", GE_FILE);
10612             }
10613             break;
10614           case MT_STALEMATE:
10615             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10616             break;
10617         }
10618         done = TRUE;
10619         break;
10620
10621       case MoveNumberOne:
10622         if (lastLoadGameStart == GNUChessGame) {
10623             /* GNUChessGames have numbers, but they aren't move numbers */
10624             if (appData.debugMode)
10625               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10626                       yy_text, (int) moveType);
10627             return LoadGameOneMove(EndOfFile); /* tail recursion */
10628         }
10629         /* else fall thru */
10630
10631       case XBoardGame:
10632       case GNUChessGame:
10633       case PGNTag:
10634         /* Reached start of next game in file */
10635         if (appData.debugMode)
10636           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10637         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10638           case MT_NONE:
10639           case MT_CHECK:
10640             break;
10641           case MT_CHECKMATE:
10642           case MT_STAINMATE:
10643             if (WhiteOnMove(currentMove)) {
10644                 GameEnds(BlackWins, "Black mates", GE_FILE);
10645             } else {
10646                 GameEnds(WhiteWins, "White mates", GE_FILE);
10647             }
10648             break;
10649           case MT_STALEMATE:
10650             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10651             break;
10652         }
10653         done = TRUE;
10654         break;
10655
10656       case PositionDiagram:     /* should not happen; ignore */
10657       case ElapsedTime:         /* ignore */
10658       case NAG:                 /* ignore */
10659         if (appData.debugMode)
10660           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10661                   yy_text, (int) moveType);
10662         return LoadGameOneMove(EndOfFile); /* tail recursion */
10663
10664       case IllegalMove:
10665         if (appData.testLegality) {
10666             if (appData.debugMode)
10667               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10668             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10669                     (forwardMostMove / 2) + 1,
10670                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10671             DisplayError(move, 0);
10672             done = TRUE;
10673         } else {
10674             if (appData.debugMode)
10675               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10676                       yy_text, currentMoveString);
10677             fromX = currentMoveString[0] - AAA;
10678             fromY = currentMoveString[1] - ONE;
10679             toX = currentMoveString[2] - AAA;
10680             toY = currentMoveString[3] - ONE;
10681             promoChar = currentMoveString[4];
10682         }
10683         break;
10684
10685       case AmbiguousMove:
10686         if (appData.debugMode)
10687           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10688         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10689                 (forwardMostMove / 2) + 1,
10690                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10691         DisplayError(move, 0);
10692         done = TRUE;
10693         break;
10694
10695       default:
10696       case ImpossibleMove:
10697         if (appData.debugMode)
10698           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10699         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10700                 (forwardMostMove / 2) + 1,
10701                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10702         DisplayError(move, 0);
10703         done = TRUE;
10704         break;
10705     }
10706
10707     if (done) {
10708         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10709             DrawPosition(FALSE, boards[currentMove]);
10710             DisplayBothClocks();
10711             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10712               DisplayComment(currentMove - 1, commentList[currentMove]);
10713         }
10714         (void) StopLoadGameTimer();
10715         gameFileFP = NULL;
10716         cmailOldMove = forwardMostMove;
10717         return FALSE;
10718     } else {
10719         /* currentMoveString is set as a side-effect of yylex */
10720
10721         thinkOutput[0] = NULLCHAR;
10722         MakeMove(fromX, fromY, toX, toY, promoChar);
10723         currentMove = forwardMostMove;
10724         return TRUE;
10725     }
10726 }
10727
10728 /* Load the nth game from the given file */
10729 int
10730 LoadGameFromFile(filename, n, title, useList)
10731      char *filename;
10732      int n;
10733      char *title;
10734      /*Boolean*/ int useList;
10735 {
10736     FILE *f;
10737     char buf[MSG_SIZ];
10738
10739     if (strcmp(filename, "-") == 0) {
10740         f = stdin;
10741         title = "stdin";
10742     } else {
10743         f = fopen(filename, "rb");
10744         if (f == NULL) {
10745           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10746             DisplayError(buf, errno);
10747             return FALSE;
10748         }
10749     }
10750     if (fseek(f, 0, 0) == -1) {
10751         /* f is not seekable; probably a pipe */
10752         useList = FALSE;
10753     }
10754     if (useList && n == 0) {
10755         int error = GameListBuild(f);
10756         if (error) {
10757             DisplayError(_("Cannot build game list"), error);
10758         } else if (!ListEmpty(&gameList) &&
10759                    ((ListGame *) gameList.tailPred)->number > 1) {
10760             GameListPopUp(f, title);
10761             return TRUE;
10762         }
10763         GameListDestroy();
10764         n = 1;
10765     }
10766     if (n == 0) n = 1;
10767     return LoadGame(f, n, title, FALSE);
10768 }
10769
10770
10771 void
10772 MakeRegisteredMove()
10773 {
10774     int fromX, fromY, toX, toY;
10775     char promoChar;
10776     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10777         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10778           case CMAIL_MOVE:
10779           case CMAIL_DRAW:
10780             if (appData.debugMode)
10781               fprintf(debugFP, "Restoring %s for game %d\n",
10782                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10783
10784             thinkOutput[0] = NULLCHAR;
10785             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10786             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10787             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10788             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10789             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10790             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10791             MakeMove(fromX, fromY, toX, toY, promoChar);
10792             ShowMove(fromX, fromY, toX, toY);
10793
10794             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10795               case MT_NONE:
10796               case MT_CHECK:
10797                 break;
10798
10799               case MT_CHECKMATE:
10800               case MT_STAINMATE:
10801                 if (WhiteOnMove(currentMove)) {
10802                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10803                 } else {
10804                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10805                 }
10806                 break;
10807
10808               case MT_STALEMATE:
10809                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10810                 break;
10811             }
10812
10813             break;
10814
10815           case CMAIL_RESIGN:
10816             if (WhiteOnMove(currentMove)) {
10817                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10818             } else {
10819                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10820             }
10821             break;
10822
10823           case CMAIL_ACCEPT:
10824             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10825             break;
10826
10827           default:
10828             break;
10829         }
10830     }
10831
10832     return;
10833 }
10834
10835 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10836 int
10837 CmailLoadGame(f, gameNumber, title, useList)
10838      FILE *f;
10839      int gameNumber;
10840      char *title;
10841      int useList;
10842 {
10843     int retVal;
10844
10845     if (gameNumber > nCmailGames) {
10846         DisplayError(_("No more games in this message"), 0);
10847         return FALSE;
10848     }
10849     if (f == lastLoadGameFP) {
10850         int offset = gameNumber - lastLoadGameNumber;
10851         if (offset == 0) {
10852             cmailMsg[0] = NULLCHAR;
10853             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10854                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10855                 nCmailMovesRegistered--;
10856             }
10857             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10858             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10859                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10860             }
10861         } else {
10862             if (! RegisterMove()) return FALSE;
10863         }
10864     }
10865
10866     retVal = LoadGame(f, gameNumber, title, useList);
10867
10868     /* Make move registered during previous look at this game, if any */
10869     MakeRegisteredMove();
10870
10871     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10872         commentList[currentMove]
10873           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10874         DisplayComment(currentMove - 1, commentList[currentMove]);
10875     }
10876
10877     return retVal;
10878 }
10879
10880 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10881 int
10882 ReloadGame(offset)
10883      int offset;
10884 {
10885     int gameNumber = lastLoadGameNumber + offset;
10886     if (lastLoadGameFP == NULL) {
10887         DisplayError(_("No game has been loaded yet"), 0);
10888         return FALSE;
10889     }
10890     if (gameNumber <= 0) {
10891         DisplayError(_("Can't back up any further"), 0);
10892         return FALSE;
10893     }
10894     if (cmailMsgLoaded) {
10895         return CmailLoadGame(lastLoadGameFP, gameNumber,
10896                              lastLoadGameTitle, lastLoadGameUseList);
10897     } else {
10898         return LoadGame(lastLoadGameFP, gameNumber,
10899                         lastLoadGameTitle, lastLoadGameUseList);
10900     }
10901 }
10902
10903
10904
10905 /* Load the nth game from open file f */
10906 int
10907 LoadGame(f, gameNumber, title, useList)
10908      FILE *f;
10909      int gameNumber;
10910      char *title;
10911      int useList;
10912 {
10913     ChessMove cm;
10914     char buf[MSG_SIZ];
10915     int gn = gameNumber;
10916     ListGame *lg = NULL;
10917     int numPGNTags = 0;
10918     int err;
10919     GameMode oldGameMode;
10920     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10921
10922     if (appData.debugMode)
10923         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10924
10925     if (gameMode == Training )
10926         SetTrainingModeOff();
10927
10928     oldGameMode = gameMode;
10929     if (gameMode != BeginningOfGame) {
10930       Reset(FALSE, TRUE);
10931     }
10932
10933     gameFileFP = f;
10934     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10935         fclose(lastLoadGameFP);
10936     }
10937
10938     if (useList) {
10939         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10940
10941         if (lg) {
10942             fseek(f, lg->offset, 0);
10943             GameListHighlight(gameNumber);
10944             gn = 1;
10945         }
10946         else {
10947             DisplayError(_("Game number out of range"), 0);
10948             return FALSE;
10949         }
10950     } else {
10951         GameListDestroy();
10952         if (fseek(f, 0, 0) == -1) {
10953             if (f == lastLoadGameFP ?
10954                 gameNumber == lastLoadGameNumber + 1 :
10955                 gameNumber == 1) {
10956                 gn = 1;
10957             } else {
10958                 DisplayError(_("Can't seek on game file"), 0);
10959                 return FALSE;
10960             }
10961         }
10962     }
10963     lastLoadGameFP = f;
10964     lastLoadGameNumber = gameNumber;
10965     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10966     lastLoadGameUseList = useList;
10967
10968     yynewfile(f);
10969
10970     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10971       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10972                 lg->gameInfo.black);
10973             DisplayTitle(buf);
10974     } else if (*title != NULLCHAR) {
10975         if (gameNumber > 1) {
10976           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10977             DisplayTitle(buf);
10978         } else {
10979             DisplayTitle(title);
10980         }
10981     }
10982
10983     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10984         gameMode = PlayFromGameFile;
10985         ModeHighlight();
10986     }
10987
10988     currentMove = forwardMostMove = backwardMostMove = 0;
10989     CopyBoard(boards[0], initialPosition);
10990     StopClocks();
10991
10992     /*
10993      * Skip the first gn-1 games in the file.
10994      * Also skip over anything that precedes an identifiable
10995      * start of game marker, to avoid being confused by
10996      * garbage at the start of the file.  Currently
10997      * recognized start of game markers are the move number "1",
10998      * the pattern "gnuchess .* game", the pattern
10999      * "^[#;%] [^ ]* game file", and a PGN tag block.
11000      * A game that starts with one of the latter two patterns
11001      * will also have a move number 1, possibly
11002      * following a position diagram.
11003      * 5-4-02: Let's try being more lenient and allowing a game to
11004      * start with an unnumbered move.  Does that break anything?
11005      */
11006     cm = lastLoadGameStart = EndOfFile;
11007     while (gn > 0) {
11008         yyboardindex = forwardMostMove;
11009         cm = (ChessMove) Myylex();
11010         switch (cm) {
11011           case EndOfFile:
11012             if (cmailMsgLoaded) {
11013                 nCmailGames = CMAIL_MAX_GAMES - gn;
11014             } else {
11015                 Reset(TRUE, TRUE);
11016                 DisplayError(_("Game not found in file"), 0);
11017             }
11018             return FALSE;
11019
11020           case GNUChessGame:
11021           case XBoardGame:
11022             gn--;
11023             lastLoadGameStart = cm;
11024             break;
11025
11026           case MoveNumberOne:
11027             switch (lastLoadGameStart) {
11028               case GNUChessGame:
11029               case XBoardGame:
11030               case PGNTag:
11031                 break;
11032               case MoveNumberOne:
11033               case EndOfFile:
11034                 gn--;           /* count this game */
11035                 lastLoadGameStart = cm;
11036                 break;
11037               default:
11038                 /* impossible */
11039                 break;
11040             }
11041             break;
11042
11043           case PGNTag:
11044             switch (lastLoadGameStart) {
11045               case GNUChessGame:
11046               case PGNTag:
11047               case MoveNumberOne:
11048               case EndOfFile:
11049                 gn--;           /* count this game */
11050                 lastLoadGameStart = cm;
11051                 break;
11052               case XBoardGame:
11053                 lastLoadGameStart = cm; /* game counted already */
11054                 break;
11055               default:
11056                 /* impossible */
11057                 break;
11058             }
11059             if (gn > 0) {
11060                 do {
11061                     yyboardindex = forwardMostMove;
11062                     cm = (ChessMove) Myylex();
11063                 } while (cm == PGNTag || cm == Comment);
11064             }
11065             break;
11066
11067           case WhiteWins:
11068           case BlackWins:
11069           case GameIsDrawn:
11070             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11071                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11072                     != CMAIL_OLD_RESULT) {
11073                     nCmailResults ++ ;
11074                     cmailResult[  CMAIL_MAX_GAMES
11075                                 - gn - 1] = CMAIL_OLD_RESULT;
11076                 }
11077             }
11078             break;
11079
11080           case NormalMove:
11081             /* Only a NormalMove can be at the start of a game
11082              * without a position diagram. */
11083             if (lastLoadGameStart == EndOfFile ) {
11084               gn--;
11085               lastLoadGameStart = MoveNumberOne;
11086             }
11087             break;
11088
11089           default:
11090             break;
11091         }
11092     }
11093
11094     if (appData.debugMode)
11095       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11096
11097     if (cm == XBoardGame) {
11098         /* Skip any header junk before position diagram and/or move 1 */
11099         for (;;) {
11100             yyboardindex = forwardMostMove;
11101             cm = (ChessMove) Myylex();
11102
11103             if (cm == EndOfFile ||
11104                 cm == GNUChessGame || cm == XBoardGame) {
11105                 /* Empty game; pretend end-of-file and handle later */
11106                 cm = EndOfFile;
11107                 break;
11108             }
11109
11110             if (cm == MoveNumberOne || cm == PositionDiagram ||
11111                 cm == PGNTag || cm == Comment)
11112               break;
11113         }
11114     } else if (cm == GNUChessGame) {
11115         if (gameInfo.event != NULL) {
11116             free(gameInfo.event);
11117         }
11118         gameInfo.event = StrSave(yy_text);
11119     }
11120
11121     startedFromSetupPosition = FALSE;
11122     while (cm == PGNTag) {
11123         if (appData.debugMode)
11124           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11125         err = ParsePGNTag(yy_text, &gameInfo);
11126         if (!err) numPGNTags++;
11127
11128         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11129         if(gameInfo.variant != oldVariant) {
11130             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11131             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11132             InitPosition(TRUE);
11133             oldVariant = gameInfo.variant;
11134             if (appData.debugMode)
11135               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11136         }
11137
11138
11139         if (gameInfo.fen != NULL) {
11140           Board initial_position;
11141           startedFromSetupPosition = TRUE;
11142           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11143             Reset(TRUE, TRUE);
11144             DisplayError(_("Bad FEN position in file"), 0);
11145             return FALSE;
11146           }
11147           CopyBoard(boards[0], initial_position);
11148           if (blackPlaysFirst) {
11149             currentMove = forwardMostMove = backwardMostMove = 1;
11150             CopyBoard(boards[1], initial_position);
11151             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11152             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11153             timeRemaining[0][1] = whiteTimeRemaining;
11154             timeRemaining[1][1] = blackTimeRemaining;
11155             if (commentList[0] != NULL) {
11156               commentList[1] = commentList[0];
11157               commentList[0] = NULL;
11158             }
11159           } else {
11160             currentMove = forwardMostMove = backwardMostMove = 0;
11161           }
11162           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11163           {   int i;
11164               initialRulePlies = FENrulePlies;
11165               for( i=0; i< nrCastlingRights; i++ )
11166                   initialRights[i] = initial_position[CASTLING][i];
11167           }
11168           yyboardindex = forwardMostMove;
11169           free(gameInfo.fen);
11170           gameInfo.fen = NULL;
11171         }
11172
11173         yyboardindex = forwardMostMove;
11174         cm = (ChessMove) Myylex();
11175
11176         /* Handle comments interspersed among the tags */
11177         while (cm == Comment) {
11178             char *p;
11179             if (appData.debugMode)
11180               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11181             p = yy_text;
11182             AppendComment(currentMove, p, FALSE);
11183             yyboardindex = forwardMostMove;
11184             cm = (ChessMove) Myylex();
11185         }
11186     }
11187
11188     /* don't rely on existence of Event tag since if game was
11189      * pasted from clipboard the Event tag may not exist
11190      */
11191     if (numPGNTags > 0){
11192         char *tags;
11193         if (gameInfo.variant == VariantNormal) {
11194           VariantClass v = StringToVariant(gameInfo.event);
11195           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11196           if(v < VariantShogi) gameInfo.variant = v;
11197         }
11198         if (!matchMode) {
11199           if( appData.autoDisplayTags ) {
11200             tags = PGNTags(&gameInfo);
11201             TagsPopUp(tags, CmailMsg());
11202             free(tags);
11203           }
11204         }
11205     } else {
11206         /* Make something up, but don't display it now */
11207         SetGameInfo();
11208         TagsPopDown();
11209     }
11210
11211     if (cm == PositionDiagram) {
11212         int i, j;
11213         char *p;
11214         Board initial_position;
11215
11216         if (appData.debugMode)
11217           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11218
11219         if (!startedFromSetupPosition) {
11220             p = yy_text;
11221             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11222               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11223                 switch (*p) {
11224                   case '{':
11225                   case '[':
11226                   case '-':
11227                   case ' ':
11228                   case '\t':
11229                   case '\n':
11230                   case '\r':
11231                     break;
11232                   default:
11233                     initial_position[i][j++] = CharToPiece(*p);
11234                     break;
11235                 }
11236             while (*p == ' ' || *p == '\t' ||
11237                    *p == '\n' || *p == '\r') p++;
11238
11239             if (strncmp(p, "black", strlen("black"))==0)
11240               blackPlaysFirst = TRUE;
11241             else
11242               blackPlaysFirst = FALSE;
11243             startedFromSetupPosition = TRUE;
11244
11245             CopyBoard(boards[0], initial_position);
11246             if (blackPlaysFirst) {
11247                 currentMove = forwardMostMove = backwardMostMove = 1;
11248                 CopyBoard(boards[1], initial_position);
11249                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11250                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11251                 timeRemaining[0][1] = whiteTimeRemaining;
11252                 timeRemaining[1][1] = blackTimeRemaining;
11253                 if (commentList[0] != NULL) {
11254                     commentList[1] = commentList[0];
11255                     commentList[0] = NULL;
11256                 }
11257             } else {
11258                 currentMove = forwardMostMove = backwardMostMove = 0;
11259             }
11260         }
11261         yyboardindex = forwardMostMove;
11262         cm = (ChessMove) Myylex();
11263     }
11264
11265     if (first.pr == NoProc) {
11266         StartChessProgram(&first);
11267     }
11268     InitChessProgram(&first, FALSE);
11269     SendToProgram("force\n", &first);
11270     if (startedFromSetupPosition) {
11271         SendBoard(&first, forwardMostMove);
11272     if (appData.debugMode) {
11273         fprintf(debugFP, "Load Game\n");
11274     }
11275         DisplayBothClocks();
11276     }
11277
11278     /* [HGM] server: flag to write setup moves in broadcast file as one */
11279     loadFlag = appData.suppressLoadMoves;
11280
11281     while (cm == Comment) {
11282         char *p;
11283         if (appData.debugMode)
11284           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11285         p = yy_text;
11286         AppendComment(currentMove, p, FALSE);
11287         yyboardindex = forwardMostMove;
11288         cm = (ChessMove) Myylex();
11289     }
11290
11291     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11292         cm == WhiteWins || cm == BlackWins ||
11293         cm == GameIsDrawn || cm == GameUnfinished) {
11294         DisplayMessage("", _("No moves in game"));
11295         if (cmailMsgLoaded) {
11296             if (appData.debugMode)
11297               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11298             ClearHighlights();
11299             flipView = FALSE;
11300         }
11301         DrawPosition(FALSE, boards[currentMove]);
11302         DisplayBothClocks();
11303         gameMode = EditGame;
11304         ModeHighlight();
11305         gameFileFP = NULL;
11306         cmailOldMove = 0;
11307         return TRUE;
11308     }
11309
11310     // [HGM] PV info: routine tests if comment empty
11311     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11312         DisplayComment(currentMove - 1, commentList[currentMove]);
11313     }
11314     if (!matchMode && appData.timeDelay != 0)
11315       DrawPosition(FALSE, boards[currentMove]);
11316
11317     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11318       programStats.ok_to_send = 1;
11319     }
11320
11321     /* if the first token after the PGN tags is a move
11322      * and not move number 1, retrieve it from the parser
11323      */
11324     if (cm != MoveNumberOne)
11325         LoadGameOneMove(cm);
11326
11327     /* load the remaining moves from the file */
11328     while (LoadGameOneMove(EndOfFile)) {
11329       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11330       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11331     }
11332
11333     /* rewind to the start of the game */
11334     currentMove = backwardMostMove;
11335
11336     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11337
11338     if (oldGameMode == AnalyzeFile ||
11339         oldGameMode == AnalyzeMode) {
11340       AnalyzeFileEvent();
11341     }
11342
11343     if (matchMode || appData.timeDelay == 0) {
11344       ToEndEvent();
11345       gameMode = EditGame;
11346       ModeHighlight();
11347     } else if (appData.timeDelay > 0) {
11348       AutoPlayGameLoop();
11349     }
11350
11351     if (appData.debugMode)
11352         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11353
11354     loadFlag = 0; /* [HGM] true game starts */
11355     return TRUE;
11356 }
11357
11358 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11359 int
11360 ReloadPosition(offset)
11361      int offset;
11362 {
11363     int positionNumber = lastLoadPositionNumber + offset;
11364     if (lastLoadPositionFP == NULL) {
11365         DisplayError(_("No position has been loaded yet"), 0);
11366         return FALSE;
11367     }
11368     if (positionNumber <= 0) {
11369         DisplayError(_("Can't back up any further"), 0);
11370         return FALSE;
11371     }
11372     return LoadPosition(lastLoadPositionFP, positionNumber,
11373                         lastLoadPositionTitle);
11374 }
11375
11376 /* Load the nth position from the given file */
11377 int
11378 LoadPositionFromFile(filename, n, title)
11379      char *filename;
11380      int n;
11381      char *title;
11382 {
11383     FILE *f;
11384     char buf[MSG_SIZ];
11385
11386     if (strcmp(filename, "-") == 0) {
11387         return LoadPosition(stdin, n, "stdin");
11388     } else {
11389         f = fopen(filename, "rb");
11390         if (f == NULL) {
11391             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11392             DisplayError(buf, errno);
11393             return FALSE;
11394         } else {
11395             return LoadPosition(f, n, title);
11396         }
11397     }
11398 }
11399
11400 /* Load the nth position from the given open file, and close it */
11401 int
11402 LoadPosition(f, positionNumber, title)
11403      FILE *f;
11404      int positionNumber;
11405      char *title;
11406 {
11407     char *p, line[MSG_SIZ];
11408     Board initial_position;
11409     int i, j, fenMode, pn;
11410
11411     if (gameMode == Training )
11412         SetTrainingModeOff();
11413
11414     if (gameMode != BeginningOfGame) {
11415         Reset(FALSE, TRUE);
11416     }
11417     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11418         fclose(lastLoadPositionFP);
11419     }
11420     if (positionNumber == 0) positionNumber = 1;
11421     lastLoadPositionFP = f;
11422     lastLoadPositionNumber = positionNumber;
11423     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11424     if (first.pr == NoProc) {
11425       StartChessProgram(&first);
11426       InitChessProgram(&first, FALSE);
11427     }
11428     pn = positionNumber;
11429     if (positionNumber < 0) {
11430         /* Negative position number means to seek to that byte offset */
11431         if (fseek(f, -positionNumber, 0) == -1) {
11432             DisplayError(_("Can't seek on position file"), 0);
11433             return FALSE;
11434         };
11435         pn = 1;
11436     } else {
11437         if (fseek(f, 0, 0) == -1) {
11438             if (f == lastLoadPositionFP ?
11439                 positionNumber == lastLoadPositionNumber + 1 :
11440                 positionNumber == 1) {
11441                 pn = 1;
11442             } else {
11443                 DisplayError(_("Can't seek on position file"), 0);
11444                 return FALSE;
11445             }
11446         }
11447     }
11448     /* See if this file is FEN or old-style xboard */
11449     if (fgets(line, MSG_SIZ, f) == NULL) {
11450         DisplayError(_("Position not found in file"), 0);
11451         return FALSE;
11452     }
11453     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11454     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11455
11456     if (pn >= 2) {
11457         if (fenMode || line[0] == '#') pn--;
11458         while (pn > 0) {
11459             /* skip positions before number pn */
11460             if (fgets(line, MSG_SIZ, f) == NULL) {
11461                 Reset(TRUE, TRUE);
11462                 DisplayError(_("Position not found in file"), 0);
11463                 return FALSE;
11464             }
11465             if (fenMode || line[0] == '#') pn--;
11466         }
11467     }
11468
11469     if (fenMode) {
11470         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11471             DisplayError(_("Bad FEN position in file"), 0);
11472             return FALSE;
11473         }
11474     } else {
11475         (void) fgets(line, MSG_SIZ, f);
11476         (void) fgets(line, MSG_SIZ, f);
11477
11478         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11479             (void) fgets(line, MSG_SIZ, f);
11480             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11481                 if (*p == ' ')
11482                   continue;
11483                 initial_position[i][j++] = CharToPiece(*p);
11484             }
11485         }
11486
11487         blackPlaysFirst = FALSE;
11488         if (!feof(f)) {
11489             (void) fgets(line, MSG_SIZ, f);
11490             if (strncmp(line, "black", strlen("black"))==0)
11491               blackPlaysFirst = TRUE;
11492         }
11493     }
11494     startedFromSetupPosition = TRUE;
11495
11496     SendToProgram("force\n", &first);
11497     CopyBoard(boards[0], initial_position);
11498     if (blackPlaysFirst) {
11499         currentMove = forwardMostMove = backwardMostMove = 1;
11500         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11501         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11502         CopyBoard(boards[1], initial_position);
11503         DisplayMessage("", _("Black to play"));
11504     } else {
11505         currentMove = forwardMostMove = backwardMostMove = 0;
11506         DisplayMessage("", _("White to play"));
11507     }
11508     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11509     SendBoard(&first, forwardMostMove);
11510     if (appData.debugMode) {
11511 int i, j;
11512   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11513   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11514         fprintf(debugFP, "Load Position\n");
11515     }
11516
11517     if (positionNumber > 1) {
11518       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11519         DisplayTitle(line);
11520     } else {
11521         DisplayTitle(title);
11522     }
11523     gameMode = EditGame;
11524     ModeHighlight();
11525     ResetClocks();
11526     timeRemaining[0][1] = whiteTimeRemaining;
11527     timeRemaining[1][1] = blackTimeRemaining;
11528     DrawPosition(FALSE, boards[currentMove]);
11529
11530     return TRUE;
11531 }
11532
11533
11534 void
11535 CopyPlayerNameIntoFileName(dest, src)
11536      char **dest, *src;
11537 {
11538     while (*src != NULLCHAR && *src != ',') {
11539         if (*src == ' ') {
11540             *(*dest)++ = '_';
11541             src++;
11542         } else {
11543             *(*dest)++ = *src++;
11544         }
11545     }
11546 }
11547
11548 char *DefaultFileName(ext)
11549      char *ext;
11550 {
11551     static char def[MSG_SIZ];
11552     char *p;
11553
11554     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11555         p = def;
11556         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11557         *p++ = '-';
11558         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11559         *p++ = '.';
11560         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11561     } else {
11562         def[0] = NULLCHAR;
11563     }
11564     return def;
11565 }
11566
11567 /* Save the current game to the given file */
11568 int
11569 SaveGameToFile(filename, append)
11570      char *filename;
11571      int append;
11572 {
11573     FILE *f;
11574     char buf[MSG_SIZ];
11575     int result;
11576
11577     if (strcmp(filename, "-") == 0) {
11578         return SaveGame(stdout, 0, NULL);
11579     } else {
11580         f = fopen(filename, append ? "a" : "w");
11581         if (f == NULL) {
11582             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11583             DisplayError(buf, errno);
11584             return FALSE;
11585         } else {
11586             safeStrCpy(buf, lastMsg, MSG_SIZ);
11587             DisplayMessage(_("Waiting for access to save file"), "");
11588             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11589             DisplayMessage(_("Saving game"), "");
11590             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11591             result = SaveGame(f, 0, NULL);
11592             DisplayMessage(buf, "");
11593             return result;
11594         }
11595     }
11596 }
11597
11598 char *
11599 SavePart(str)
11600      char *str;
11601 {
11602     static char buf[MSG_SIZ];
11603     char *p;
11604
11605     p = strchr(str, ' ');
11606     if (p == NULL) return str;
11607     strncpy(buf, str, p - str);
11608     buf[p - str] = NULLCHAR;
11609     return buf;
11610 }
11611
11612 #define PGN_MAX_LINE 75
11613
11614 #define PGN_SIDE_WHITE  0
11615 #define PGN_SIDE_BLACK  1
11616
11617 /* [AS] */
11618 static int FindFirstMoveOutOfBook( int side )
11619 {
11620     int result = -1;
11621
11622     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11623         int index = backwardMostMove;
11624         int has_book_hit = 0;
11625
11626         if( (index % 2) != side ) {
11627             index++;
11628         }
11629
11630         while( index < forwardMostMove ) {
11631             /* Check to see if engine is in book */
11632             int depth = pvInfoList[index].depth;
11633             int score = pvInfoList[index].score;
11634             int in_book = 0;
11635
11636             if( depth <= 2 ) {
11637                 in_book = 1;
11638             }
11639             else if( score == 0 && depth == 63 ) {
11640                 in_book = 1; /* Zappa */
11641             }
11642             else if( score == 2 && depth == 99 ) {
11643                 in_book = 1; /* Abrok */
11644             }
11645
11646             has_book_hit += in_book;
11647
11648             if( ! in_book ) {
11649                 result = index;
11650
11651                 break;
11652             }
11653
11654             index += 2;
11655         }
11656     }
11657
11658     return result;
11659 }
11660
11661 /* [AS] */
11662 void GetOutOfBookInfo( char * buf )
11663 {
11664     int oob[2];
11665     int i;
11666     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11667
11668     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11669     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11670
11671     *buf = '\0';
11672
11673     if( oob[0] >= 0 || oob[1] >= 0 ) {
11674         for( i=0; i<2; i++ ) {
11675             int idx = oob[i];
11676
11677             if( idx >= 0 ) {
11678                 if( i > 0 && oob[0] >= 0 ) {
11679                     strcat( buf, "   " );
11680                 }
11681
11682                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11683                 sprintf( buf+strlen(buf), "%s%.2f",
11684                     pvInfoList[idx].score >= 0 ? "+" : "",
11685                     pvInfoList[idx].score / 100.0 );
11686             }
11687         }
11688     }
11689 }
11690
11691 /* Save game in PGN style and close the file */
11692 int
11693 SaveGamePGN(f)
11694      FILE *f;
11695 {
11696     int i, offset, linelen, newblock;
11697     time_t tm;
11698 //    char *movetext;
11699     char numtext[32];
11700     int movelen, numlen, blank;
11701     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11702
11703     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11704
11705     tm = time((time_t *) NULL);
11706
11707     PrintPGNTags(f, &gameInfo);
11708
11709     if (backwardMostMove > 0 || startedFromSetupPosition) {
11710         char *fen = PositionToFEN(backwardMostMove, NULL);
11711         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11712         fprintf(f, "\n{--------------\n");
11713         PrintPosition(f, backwardMostMove);
11714         fprintf(f, "--------------}\n");
11715         free(fen);
11716     }
11717     else {
11718         /* [AS] Out of book annotation */
11719         if( appData.saveOutOfBookInfo ) {
11720             char buf[64];
11721
11722             GetOutOfBookInfo( buf );
11723
11724             if( buf[0] != '\0' ) {
11725                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11726             }
11727         }
11728
11729         fprintf(f, "\n");
11730     }
11731
11732     i = backwardMostMove;
11733     linelen = 0;
11734     newblock = TRUE;
11735
11736     while (i < forwardMostMove) {
11737         /* Print comments preceding this move */
11738         if (commentList[i] != NULL) {
11739             if (linelen > 0) fprintf(f, "\n");
11740             fprintf(f, "%s", commentList[i]);
11741             linelen = 0;
11742             newblock = TRUE;
11743         }
11744
11745         /* Format move number */
11746         if ((i % 2) == 0)
11747           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11748         else
11749           if (newblock)
11750             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11751           else
11752             numtext[0] = NULLCHAR;
11753
11754         numlen = strlen(numtext);
11755         newblock = FALSE;
11756
11757         /* Print move number */
11758         blank = linelen > 0 && numlen > 0;
11759         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11760             fprintf(f, "\n");
11761             linelen = 0;
11762             blank = 0;
11763         }
11764         if (blank) {
11765             fprintf(f, " ");
11766             linelen++;
11767         }
11768         fprintf(f, "%s", numtext);
11769         linelen += numlen;
11770
11771         /* Get move */
11772         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11773         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11774
11775         /* Print move */
11776         blank = linelen > 0 && movelen > 0;
11777         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11778             fprintf(f, "\n");
11779             linelen = 0;
11780             blank = 0;
11781         }
11782         if (blank) {
11783             fprintf(f, " ");
11784             linelen++;
11785         }
11786         fprintf(f, "%s", move_buffer);
11787         linelen += movelen;
11788
11789         /* [AS] Add PV info if present */
11790         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11791             /* [HGM] add time */
11792             char buf[MSG_SIZ]; int seconds;
11793
11794             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11795
11796             if( seconds <= 0)
11797               buf[0] = 0;
11798             else
11799               if( seconds < 30 )
11800                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11801               else
11802                 {
11803                   seconds = (seconds + 4)/10; // round to full seconds
11804                   if( seconds < 60 )
11805                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11806                   else
11807                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11808                 }
11809
11810             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11811                       pvInfoList[i].score >= 0 ? "+" : "",
11812                       pvInfoList[i].score / 100.0,
11813                       pvInfoList[i].depth,
11814                       buf );
11815
11816             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11817
11818             /* Print score/depth */
11819             blank = linelen > 0 && movelen > 0;
11820             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11821                 fprintf(f, "\n");
11822                 linelen = 0;
11823                 blank = 0;
11824             }
11825             if (blank) {
11826                 fprintf(f, " ");
11827                 linelen++;
11828             }
11829             fprintf(f, "%s", move_buffer);
11830             linelen += movelen;
11831         }
11832
11833         i++;
11834     }
11835
11836     /* Start a new line */
11837     if (linelen > 0) fprintf(f, "\n");
11838
11839     /* Print comments after last move */
11840     if (commentList[i] != NULL) {
11841         fprintf(f, "%s\n", commentList[i]);
11842     }
11843
11844     /* Print result */
11845     if (gameInfo.resultDetails != NULL &&
11846         gameInfo.resultDetails[0] != NULLCHAR) {
11847         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11848                 PGNResult(gameInfo.result));
11849     } else {
11850         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11851     }
11852
11853     fclose(f);
11854     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11855     return TRUE;
11856 }
11857
11858 /* Save game in old style and close the file */
11859 int
11860 SaveGameOldStyle(f)
11861      FILE *f;
11862 {
11863     int i, offset;
11864     time_t tm;
11865
11866     tm = time((time_t *) NULL);
11867
11868     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11869     PrintOpponents(f);
11870
11871     if (backwardMostMove > 0 || startedFromSetupPosition) {
11872         fprintf(f, "\n[--------------\n");
11873         PrintPosition(f, backwardMostMove);
11874         fprintf(f, "--------------]\n");
11875     } else {
11876         fprintf(f, "\n");
11877     }
11878
11879     i = backwardMostMove;
11880     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11881
11882     while (i < forwardMostMove) {
11883         if (commentList[i] != NULL) {
11884             fprintf(f, "[%s]\n", commentList[i]);
11885         }
11886
11887         if ((i % 2) == 1) {
11888             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11889             i++;
11890         } else {
11891             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11892             i++;
11893             if (commentList[i] != NULL) {
11894                 fprintf(f, "\n");
11895                 continue;
11896             }
11897             if (i >= forwardMostMove) {
11898                 fprintf(f, "\n");
11899                 break;
11900             }
11901             fprintf(f, "%s\n", parseList[i]);
11902             i++;
11903         }
11904     }
11905
11906     if (commentList[i] != NULL) {
11907         fprintf(f, "[%s]\n", commentList[i]);
11908     }
11909
11910     /* This isn't really the old style, but it's close enough */
11911     if (gameInfo.resultDetails != NULL &&
11912         gameInfo.resultDetails[0] != NULLCHAR) {
11913         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11914                 gameInfo.resultDetails);
11915     } else {
11916         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11917     }
11918
11919     fclose(f);
11920     return TRUE;
11921 }
11922
11923 /* Save the current game to open file f and close the file */
11924 int
11925 SaveGame(f, dummy, dummy2)
11926      FILE *f;
11927      int dummy;
11928      char *dummy2;
11929 {
11930     if (gameMode == EditPosition) EditPositionDone(TRUE);
11931     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11932     if (appData.oldSaveStyle)
11933       return SaveGameOldStyle(f);
11934     else
11935       return SaveGamePGN(f);
11936 }
11937
11938 /* Save the current position to the given file */
11939 int
11940 SavePositionToFile(filename)
11941      char *filename;
11942 {
11943     FILE *f;
11944     char buf[MSG_SIZ];
11945
11946     if (strcmp(filename, "-") == 0) {
11947         return SavePosition(stdout, 0, NULL);
11948     } else {
11949         f = fopen(filename, "a");
11950         if (f == NULL) {
11951             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11952             DisplayError(buf, errno);
11953             return FALSE;
11954         } else {
11955             safeStrCpy(buf, lastMsg, MSG_SIZ);
11956             DisplayMessage(_("Waiting for access to save file"), "");
11957             flock(fileno(f), LOCK_EX); // [HGM] lock
11958             DisplayMessage(_("Saving position"), "");
11959             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11960             SavePosition(f, 0, NULL);
11961             DisplayMessage(buf, "");
11962             return TRUE;
11963         }
11964     }
11965 }
11966
11967 /* Save the current position to the given open file and close the file */
11968 int
11969 SavePosition(f, dummy, dummy2)
11970      FILE *f;
11971      int dummy;
11972      char *dummy2;
11973 {
11974     time_t tm;
11975     char *fen;
11976
11977     if (gameMode == EditPosition) EditPositionDone(TRUE);
11978     if (appData.oldSaveStyle) {
11979         tm = time((time_t *) NULL);
11980
11981         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11982         PrintOpponents(f);
11983         fprintf(f, "[--------------\n");
11984         PrintPosition(f, currentMove);
11985         fprintf(f, "--------------]\n");
11986     } else {
11987         fen = PositionToFEN(currentMove, NULL);
11988         fprintf(f, "%s\n", fen);
11989         free(fen);
11990     }
11991     fclose(f);
11992     return TRUE;
11993 }
11994
11995 void
11996 ReloadCmailMsgEvent(unregister)
11997      int unregister;
11998 {
11999 #if !WIN32
12000     static char *inFilename = NULL;
12001     static char *outFilename;
12002     int i;
12003     struct stat inbuf, outbuf;
12004     int status;
12005
12006     /* Any registered moves are unregistered if unregister is set, */
12007     /* i.e. invoked by the signal handler */
12008     if (unregister) {
12009         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12010             cmailMoveRegistered[i] = FALSE;
12011             if (cmailCommentList[i] != NULL) {
12012                 free(cmailCommentList[i]);
12013                 cmailCommentList[i] = NULL;
12014             }
12015         }
12016         nCmailMovesRegistered = 0;
12017     }
12018
12019     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12020         cmailResult[i] = CMAIL_NOT_RESULT;
12021     }
12022     nCmailResults = 0;
12023
12024     if (inFilename == NULL) {
12025         /* Because the filenames are static they only get malloced once  */
12026         /* and they never get freed                                      */
12027         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12028         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12029
12030         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12031         sprintf(outFilename, "%s.out", appData.cmailGameName);
12032     }
12033
12034     status = stat(outFilename, &outbuf);
12035     if (status < 0) {
12036         cmailMailedMove = FALSE;
12037     } else {
12038         status = stat(inFilename, &inbuf);
12039         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12040     }
12041
12042     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12043        counts the games, notes how each one terminated, etc.
12044
12045        It would be nice to remove this kludge and instead gather all
12046        the information while building the game list.  (And to keep it
12047        in the game list nodes instead of having a bunch of fixed-size
12048        parallel arrays.)  Note this will require getting each game's
12049        termination from the PGN tags, as the game list builder does
12050        not process the game moves.  --mann
12051        */
12052     cmailMsgLoaded = TRUE;
12053     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12054
12055     /* Load first game in the file or popup game menu */
12056     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12057
12058 #endif /* !WIN32 */
12059     return;
12060 }
12061
12062 int
12063 RegisterMove()
12064 {
12065     FILE *f;
12066     char string[MSG_SIZ];
12067
12068     if (   cmailMailedMove
12069         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12070         return TRUE;            /* Allow free viewing  */
12071     }
12072
12073     /* Unregister move to ensure that we don't leave RegisterMove        */
12074     /* with the move registered when the conditions for registering no   */
12075     /* longer hold                                                       */
12076     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12077         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12078         nCmailMovesRegistered --;
12079
12080         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12081           {
12082               free(cmailCommentList[lastLoadGameNumber - 1]);
12083               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12084           }
12085     }
12086
12087     if (cmailOldMove == -1) {
12088         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12089         return FALSE;
12090     }
12091
12092     if (currentMove > cmailOldMove + 1) {
12093         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12094         return FALSE;
12095     }
12096
12097     if (currentMove < cmailOldMove) {
12098         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12099         return FALSE;
12100     }
12101
12102     if (forwardMostMove > currentMove) {
12103         /* Silently truncate extra moves */
12104         TruncateGame();
12105     }
12106
12107     if (   (currentMove == cmailOldMove + 1)
12108         || (   (currentMove == cmailOldMove)
12109             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12110                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12111         if (gameInfo.result != GameUnfinished) {
12112             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12113         }
12114
12115         if (commentList[currentMove] != NULL) {
12116             cmailCommentList[lastLoadGameNumber - 1]
12117               = StrSave(commentList[currentMove]);
12118         }
12119         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12120
12121         if (appData.debugMode)
12122           fprintf(debugFP, "Saving %s for game %d\n",
12123                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12124
12125         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12126
12127         f = fopen(string, "w");
12128         if (appData.oldSaveStyle) {
12129             SaveGameOldStyle(f); /* also closes the file */
12130
12131             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12132             f = fopen(string, "w");
12133             SavePosition(f, 0, NULL); /* also closes the file */
12134         } else {
12135             fprintf(f, "{--------------\n");
12136             PrintPosition(f, currentMove);
12137             fprintf(f, "--------------}\n\n");
12138
12139             SaveGame(f, 0, NULL); /* also closes the file*/
12140         }
12141
12142         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12143         nCmailMovesRegistered ++;
12144     } else if (nCmailGames == 1) {
12145         DisplayError(_("You have not made a move yet"), 0);
12146         return FALSE;
12147     }
12148
12149     return TRUE;
12150 }
12151
12152 void
12153 MailMoveEvent()
12154 {
12155 #if !WIN32
12156     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12157     FILE *commandOutput;
12158     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12159     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12160     int nBuffers;
12161     int i;
12162     int archived;
12163     char *arcDir;
12164
12165     if (! cmailMsgLoaded) {
12166         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12167         return;
12168     }
12169
12170     if (nCmailGames == nCmailResults) {
12171         DisplayError(_("No unfinished games"), 0);
12172         return;
12173     }
12174
12175 #if CMAIL_PROHIBIT_REMAIL
12176     if (cmailMailedMove) {
12177       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);
12178         DisplayError(msg, 0);
12179         return;
12180     }
12181 #endif
12182
12183     if (! (cmailMailedMove || RegisterMove())) return;
12184
12185     if (   cmailMailedMove
12186         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12187       snprintf(string, MSG_SIZ, partCommandString,
12188                appData.debugMode ? " -v" : "", appData.cmailGameName);
12189         commandOutput = popen(string, "r");
12190
12191         if (commandOutput == NULL) {
12192             DisplayError(_("Failed to invoke cmail"), 0);
12193         } else {
12194             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12195                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12196             }
12197             if (nBuffers > 1) {
12198                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12199                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12200                 nBytes = MSG_SIZ - 1;
12201             } else {
12202                 (void) memcpy(msg, buffer, nBytes);
12203             }
12204             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12205
12206             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12207                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12208
12209                 archived = TRUE;
12210                 for (i = 0; i < nCmailGames; i ++) {
12211                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12212                         archived = FALSE;
12213                     }
12214                 }
12215                 if (   archived
12216                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12217                         != NULL)) {
12218                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12219                            arcDir,
12220                            appData.cmailGameName,
12221                            gameInfo.date);
12222                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12223                     cmailMsgLoaded = FALSE;
12224                 }
12225             }
12226
12227             DisplayInformation(msg);
12228             pclose(commandOutput);
12229         }
12230     } else {
12231         if ((*cmailMsg) != '\0') {
12232             DisplayInformation(cmailMsg);
12233         }
12234     }
12235
12236     return;
12237 #endif /* !WIN32 */
12238 }
12239
12240 char *
12241 CmailMsg()
12242 {
12243 #if WIN32
12244     return NULL;
12245 #else
12246     int  prependComma = 0;
12247     char number[5];
12248     char string[MSG_SIZ];       /* Space for game-list */
12249     int  i;
12250
12251     if (!cmailMsgLoaded) return "";
12252
12253     if (cmailMailedMove) {
12254       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12255     } else {
12256         /* Create a list of games left */
12257       snprintf(string, MSG_SIZ, "[");
12258         for (i = 0; i < nCmailGames; i ++) {
12259             if (! (   cmailMoveRegistered[i]
12260                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12261                 if (prependComma) {
12262                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12263                 } else {
12264                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12265                     prependComma = 1;
12266                 }
12267
12268                 strcat(string, number);
12269             }
12270         }
12271         strcat(string, "]");
12272
12273         if (nCmailMovesRegistered + nCmailResults == 0) {
12274             switch (nCmailGames) {
12275               case 1:
12276                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12277                 break;
12278
12279               case 2:
12280                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12281                 break;
12282
12283               default:
12284                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12285                          nCmailGames);
12286                 break;
12287             }
12288         } else {
12289             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12290               case 1:
12291                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12292                          string);
12293                 break;
12294
12295               case 0:
12296                 if (nCmailResults == nCmailGames) {
12297                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12298                 } else {
12299                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12300                 }
12301                 break;
12302
12303               default:
12304                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12305                          string);
12306             }
12307         }
12308     }
12309     return cmailMsg;
12310 #endif /* WIN32 */
12311 }
12312
12313 void
12314 ResetGameEvent()
12315 {
12316     if (gameMode == Training)
12317       SetTrainingModeOff();
12318
12319     Reset(TRUE, TRUE);
12320     cmailMsgLoaded = FALSE;
12321     if (appData.icsActive) {
12322       SendToICS(ics_prefix);
12323       SendToICS("refresh\n");
12324     }
12325 }
12326
12327 void
12328 ExitEvent(status)
12329      int status;
12330 {
12331     exiting++;
12332     if (exiting > 2) {
12333       /* Give up on clean exit */
12334       exit(status);
12335     }
12336     if (exiting > 1) {
12337       /* Keep trying for clean exit */
12338       return;
12339     }
12340
12341     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12342
12343     if (telnetISR != NULL) {
12344       RemoveInputSource(telnetISR);
12345     }
12346     if (icsPR != NoProc) {
12347       DestroyChildProcess(icsPR, TRUE);
12348     }
12349
12350     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12351     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12352
12353     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12354     /* make sure this other one finishes before killing it!                  */
12355     if(endingGame) { int count = 0;
12356         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12357         while(endingGame && count++ < 10) DoSleep(1);
12358         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12359     }
12360
12361     /* Kill off chess programs */
12362     if (first.pr != NoProc) {
12363         ExitAnalyzeMode();
12364
12365         DoSleep( appData.delayBeforeQuit );
12366         SendToProgram("quit\n", &first);
12367         DoSleep( appData.delayAfterQuit );
12368         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12369     }
12370     if (second.pr != NoProc) {
12371         DoSleep( appData.delayBeforeQuit );
12372         SendToProgram("quit\n", &second);
12373         DoSleep( appData.delayAfterQuit );
12374         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12375     }
12376     if (first.isr != NULL) {
12377         RemoveInputSource(first.isr);
12378     }
12379     if (second.isr != NULL) {
12380         RemoveInputSource(second.isr);
12381     }
12382
12383     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12384     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12385
12386     ShutDownFrontEnd();
12387     exit(status);
12388 }
12389
12390 void
12391 PauseEvent()
12392 {
12393     if (appData.debugMode)
12394         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12395     if (pausing) {
12396         pausing = FALSE;
12397         ModeHighlight();
12398         if (gameMode == MachinePlaysWhite ||
12399             gameMode == MachinePlaysBlack) {
12400             StartClocks();
12401         } else {
12402             DisplayBothClocks();
12403         }
12404         if (gameMode == PlayFromGameFile) {
12405             if (appData.timeDelay >= 0)
12406                 AutoPlayGameLoop();
12407         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12408             Reset(FALSE, TRUE);
12409             SendToICS(ics_prefix);
12410             SendToICS("refresh\n");
12411         } else if (currentMove < forwardMostMove) {
12412             ForwardInner(forwardMostMove);
12413         }
12414         pauseExamInvalid = FALSE;
12415     } else {
12416         switch (gameMode) {
12417           default:
12418             return;
12419           case IcsExamining:
12420             pauseExamForwardMostMove = forwardMostMove;
12421             pauseExamInvalid = FALSE;
12422             /* fall through */
12423           case IcsObserving:
12424           case IcsPlayingWhite:
12425           case IcsPlayingBlack:
12426             pausing = TRUE;
12427             ModeHighlight();
12428             return;
12429           case PlayFromGameFile:
12430             (void) StopLoadGameTimer();
12431             pausing = TRUE;
12432             ModeHighlight();
12433             break;
12434           case BeginningOfGame:
12435             if (appData.icsActive) return;
12436             /* else fall through */
12437           case MachinePlaysWhite:
12438           case MachinePlaysBlack:
12439           case TwoMachinesPlay:
12440             if (forwardMostMove == 0)
12441               return;           /* don't pause if no one has moved */
12442             if ((gameMode == MachinePlaysWhite &&
12443                  !WhiteOnMove(forwardMostMove)) ||
12444                 (gameMode == MachinePlaysBlack &&
12445                  WhiteOnMove(forwardMostMove))) {
12446                 StopClocks();
12447             }
12448             pausing = TRUE;
12449             ModeHighlight();
12450             break;
12451         }
12452     }
12453 }
12454
12455 void
12456 EditCommentEvent()
12457 {
12458     char title[MSG_SIZ];
12459
12460     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12461       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12462     } else {
12463       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12464                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12465                parseList[currentMove - 1]);
12466     }
12467
12468     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12469 }
12470
12471
12472 void
12473 EditTagsEvent()
12474 {
12475     char *tags = PGNTags(&gameInfo);
12476     bookUp = FALSE;
12477     EditTagsPopUp(tags, NULL);
12478     free(tags);
12479 }
12480
12481 void
12482 AnalyzeModeEvent()
12483 {
12484     if (appData.noChessProgram || gameMode == AnalyzeMode)
12485       return;
12486
12487     if (gameMode != AnalyzeFile) {
12488         if (!appData.icsEngineAnalyze) {
12489                EditGameEvent();
12490                if (gameMode != EditGame) return;
12491         }
12492         ResurrectChessProgram();
12493         SendToProgram("analyze\n", &first);
12494         first.analyzing = TRUE;
12495         /*first.maybeThinking = TRUE;*/
12496         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12497         EngineOutputPopUp();
12498     }
12499     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12500     pausing = FALSE;
12501     ModeHighlight();
12502     SetGameInfo();
12503
12504     StartAnalysisClock();
12505     GetTimeMark(&lastNodeCountTime);
12506     lastNodeCount = 0;
12507 }
12508
12509 void
12510 AnalyzeFileEvent()
12511 {
12512     if (appData.noChessProgram || gameMode == AnalyzeFile)
12513       return;
12514
12515     if (gameMode != AnalyzeMode) {
12516         EditGameEvent();
12517         if (gameMode != EditGame) return;
12518         ResurrectChessProgram();
12519         SendToProgram("analyze\n", &first);
12520         first.analyzing = TRUE;
12521         /*first.maybeThinking = TRUE;*/
12522         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12523         EngineOutputPopUp();
12524     }
12525     gameMode = AnalyzeFile;
12526     pausing = FALSE;
12527     ModeHighlight();
12528     SetGameInfo();
12529
12530     StartAnalysisClock();
12531     GetTimeMark(&lastNodeCountTime);
12532     lastNodeCount = 0;
12533 }
12534
12535 void
12536 MachineWhiteEvent()
12537 {
12538     char buf[MSG_SIZ];
12539     char *bookHit = NULL;
12540
12541     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12542       return;
12543
12544
12545     if (gameMode == PlayFromGameFile ||
12546         gameMode == TwoMachinesPlay  ||
12547         gameMode == Training         ||
12548         gameMode == AnalyzeMode      ||
12549         gameMode == EndOfGame)
12550         EditGameEvent();
12551
12552     if (gameMode == EditPosition)
12553         EditPositionDone(TRUE);
12554
12555     if (!WhiteOnMove(currentMove)) {
12556         DisplayError(_("It is not White's turn"), 0);
12557         return;
12558     }
12559
12560     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12561       ExitAnalyzeMode();
12562
12563     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12564         gameMode == AnalyzeFile)
12565         TruncateGame();
12566
12567     ResurrectChessProgram();    /* in case it isn't running */
12568     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12569         gameMode = MachinePlaysWhite;
12570         ResetClocks();
12571     } else
12572     gameMode = MachinePlaysWhite;
12573     pausing = FALSE;
12574     ModeHighlight();
12575     SetGameInfo();
12576     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12577     DisplayTitle(buf);
12578     if (first.sendName) {
12579       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12580       SendToProgram(buf, &first);
12581     }
12582     if (first.sendTime) {
12583       if (first.useColors) {
12584         SendToProgram("black\n", &first); /*gnu kludge*/
12585       }
12586       SendTimeRemaining(&first, TRUE);
12587     }
12588     if (first.useColors) {
12589       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12590     }
12591     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12592     SetMachineThinkingEnables();
12593     first.maybeThinking = TRUE;
12594     StartClocks();
12595     firstMove = FALSE;
12596
12597     if (appData.autoFlipView && !flipView) {
12598       flipView = !flipView;
12599       DrawPosition(FALSE, NULL);
12600       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12601     }
12602
12603     if(bookHit) { // [HGM] book: simulate book reply
12604         static char bookMove[MSG_SIZ]; // a bit generous?
12605
12606         programStats.nodes = programStats.depth = programStats.time =
12607         programStats.score = programStats.got_only_move = 0;
12608         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12609
12610         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12611         strcat(bookMove, bookHit);
12612         HandleMachineMove(bookMove, &first);
12613     }
12614 }
12615
12616 void
12617 MachineBlackEvent()
12618 {
12619   char buf[MSG_SIZ];
12620   char *bookHit = NULL;
12621
12622     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12623         return;
12624
12625
12626     if (gameMode == PlayFromGameFile ||
12627         gameMode == TwoMachinesPlay  ||
12628         gameMode == Training         ||
12629         gameMode == AnalyzeMode      ||
12630         gameMode == EndOfGame)
12631         EditGameEvent();
12632
12633     if (gameMode == EditPosition)
12634         EditPositionDone(TRUE);
12635
12636     if (WhiteOnMove(currentMove)) {
12637         DisplayError(_("It is not Black's turn"), 0);
12638         return;
12639     }
12640
12641     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12642       ExitAnalyzeMode();
12643
12644     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12645         gameMode == AnalyzeFile)
12646         TruncateGame();
12647
12648     ResurrectChessProgram();    /* in case it isn't running */
12649     gameMode = MachinePlaysBlack;
12650     pausing = FALSE;
12651     ModeHighlight();
12652     SetGameInfo();
12653     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12654     DisplayTitle(buf);
12655     if (first.sendName) {
12656       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12657       SendToProgram(buf, &first);
12658     }
12659     if (first.sendTime) {
12660       if (first.useColors) {
12661         SendToProgram("white\n", &first); /*gnu kludge*/
12662       }
12663       SendTimeRemaining(&first, FALSE);
12664     }
12665     if (first.useColors) {
12666       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12667     }
12668     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12669     SetMachineThinkingEnables();
12670     first.maybeThinking = TRUE;
12671     StartClocks();
12672
12673     if (appData.autoFlipView && flipView) {
12674       flipView = !flipView;
12675       DrawPosition(FALSE, NULL);
12676       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12677     }
12678     if(bookHit) { // [HGM] book: simulate book reply
12679         static char bookMove[MSG_SIZ]; // a bit generous?
12680
12681         programStats.nodes = programStats.depth = programStats.time =
12682         programStats.score = programStats.got_only_move = 0;
12683         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12684
12685         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12686         strcat(bookMove, bookHit);
12687         HandleMachineMove(bookMove, &first);
12688     }
12689 }
12690
12691
12692 void
12693 DisplayTwoMachinesTitle()
12694 {
12695     char buf[MSG_SIZ];
12696     if (appData.matchGames > 0) {
12697         if(appData.tourneyFile[0]) {
12698           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12699                    gameInfo.white, gameInfo.black,
12700                    nextGame+1, appData.matchGames+1,
12701                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12702         } else 
12703         if (first.twoMachinesColor[0] == 'w') {
12704           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12705                    gameInfo.white, gameInfo.black,
12706                    first.matchWins, second.matchWins,
12707                    matchGame - 1 - (first.matchWins + second.matchWins));
12708         } else {
12709           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12710                    gameInfo.white, gameInfo.black,
12711                    second.matchWins, first.matchWins,
12712                    matchGame - 1 - (first.matchWins + second.matchWins));
12713         }
12714     } else {
12715       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12716     }
12717     DisplayTitle(buf);
12718 }
12719
12720 void
12721 SettingsMenuIfReady()
12722 {
12723   if (second.lastPing != second.lastPong) {
12724     DisplayMessage("", _("Waiting for second chess program"));
12725     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12726     return;
12727   }
12728   ThawUI();
12729   DisplayMessage("", "");
12730   SettingsPopUp(&second);
12731 }
12732
12733 int
12734 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12735 {
12736     char buf[MSG_SIZ];
12737     if (cps->pr == NULL) {
12738         StartChessProgram(cps);
12739         if (cps->protocolVersion == 1) {
12740           retry();
12741         } else {
12742           /* kludge: allow timeout for initial "feature" command */
12743           FreezeUI();
12744           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12745           DisplayMessage("", buf);
12746           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12747         }
12748         return 1;
12749     }
12750     return 0;
12751 }
12752
12753 void
12754 TwoMachinesEvent P((void))
12755 {
12756     int i;
12757     char buf[MSG_SIZ];
12758     ChessProgramState *onmove;
12759     char *bookHit = NULL;
12760     static int stalling = 0;
12761     TimeMark now;
12762     long wait;
12763
12764     if (appData.noChessProgram) return;
12765
12766     switch (gameMode) {
12767       case TwoMachinesPlay:
12768         return;
12769       case MachinePlaysWhite:
12770       case MachinePlaysBlack:
12771         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12772             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12773             return;
12774         }
12775         /* fall through */
12776       case BeginningOfGame:
12777       case PlayFromGameFile:
12778       case EndOfGame:
12779         EditGameEvent();
12780         if (gameMode != EditGame) return;
12781         break;
12782       case EditPosition:
12783         EditPositionDone(TRUE);
12784         break;
12785       case AnalyzeMode:
12786       case AnalyzeFile:
12787         ExitAnalyzeMode();
12788         break;
12789       case EditGame:
12790       default:
12791         break;
12792     }
12793
12794 //    forwardMostMove = currentMove;
12795     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12796
12797     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12798
12799     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12800     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12801       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12802       return;
12803     }
12804     if(!stalling) {
12805       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12806       SendToProgram("force\n", &second);
12807       stalling = 1;
12808       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12809       return;
12810     }
12811     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12812     if(appData.matchPause>10000 || appData.matchPause<10)
12813                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12814     wait = SubtractTimeMarks(&now, &pauseStart);
12815     if(wait < appData.matchPause) {
12816         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12817         return;
12818     }
12819     stalling = 0;
12820     DisplayMessage("", "");
12821     if (startedFromSetupPosition) {
12822         SendBoard(&second, backwardMostMove);
12823     if (appData.debugMode) {
12824         fprintf(debugFP, "Two Machines\n");
12825     }
12826     }
12827     for (i = backwardMostMove; i < forwardMostMove; i++) {
12828         SendMoveToProgram(i, &second);
12829     }
12830
12831     gameMode = TwoMachinesPlay;
12832     pausing = FALSE;
12833     ModeHighlight();
12834     SetGameInfo();
12835     DisplayTwoMachinesTitle();
12836     firstMove = TRUE;
12837     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12838         onmove = &first;
12839     } else {
12840         onmove = &second;
12841     }
12842     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12843     SendToProgram(first.computerString, &first);
12844     if (first.sendName) {
12845       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12846       SendToProgram(buf, &first);
12847     }
12848     SendToProgram(second.computerString, &second);
12849     if (second.sendName) {
12850       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12851       SendToProgram(buf, &second);
12852     }
12853
12854     ResetClocks();
12855     if (!first.sendTime || !second.sendTime) {
12856         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12857         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12858     }
12859     if (onmove->sendTime) {
12860       if (onmove->useColors) {
12861         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12862       }
12863       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12864     }
12865     if (onmove->useColors) {
12866       SendToProgram(onmove->twoMachinesColor, onmove);
12867     }
12868     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12869 //    SendToProgram("go\n", onmove);
12870     onmove->maybeThinking = TRUE;
12871     SetMachineThinkingEnables();
12872
12873     StartClocks();
12874
12875     if(bookHit) { // [HGM] book: simulate book reply
12876         static char bookMove[MSG_SIZ]; // a bit generous?
12877
12878         programStats.nodes = programStats.depth = programStats.time =
12879         programStats.score = programStats.got_only_move = 0;
12880         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12881
12882         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12883         strcat(bookMove, bookHit);
12884         savedMessage = bookMove; // args for deferred call
12885         savedState = onmove;
12886         ScheduleDelayedEvent(DeferredBookMove, 1);
12887     }
12888 }
12889
12890 void
12891 TrainingEvent()
12892 {
12893     if (gameMode == Training) {
12894       SetTrainingModeOff();
12895       gameMode = PlayFromGameFile;
12896       DisplayMessage("", _("Training mode off"));
12897     } else {
12898       gameMode = Training;
12899       animateTraining = appData.animate;
12900
12901       /* make sure we are not already at the end of the game */
12902       if (currentMove < forwardMostMove) {
12903         SetTrainingModeOn();
12904         DisplayMessage("", _("Training mode on"));
12905       } else {
12906         gameMode = PlayFromGameFile;
12907         DisplayError(_("Already at end of game"), 0);
12908       }
12909     }
12910     ModeHighlight();
12911 }
12912
12913 void
12914 IcsClientEvent()
12915 {
12916     if (!appData.icsActive) return;
12917     switch (gameMode) {
12918       case IcsPlayingWhite:
12919       case IcsPlayingBlack:
12920       case IcsObserving:
12921       case IcsIdle:
12922       case BeginningOfGame:
12923       case IcsExamining:
12924         return;
12925
12926       case EditGame:
12927         break;
12928
12929       case EditPosition:
12930         EditPositionDone(TRUE);
12931         break;
12932
12933       case AnalyzeMode:
12934       case AnalyzeFile:
12935         ExitAnalyzeMode();
12936         break;
12937
12938       default:
12939         EditGameEvent();
12940         break;
12941     }
12942
12943     gameMode = IcsIdle;
12944     ModeHighlight();
12945     return;
12946 }
12947
12948
12949 void
12950 EditGameEvent()
12951 {
12952     int i;
12953
12954     switch (gameMode) {
12955       case Training:
12956         SetTrainingModeOff();
12957         break;
12958       case MachinePlaysWhite:
12959       case MachinePlaysBlack:
12960       case BeginningOfGame:
12961         SendToProgram("force\n", &first);
12962         SetUserThinkingEnables();
12963         break;
12964       case PlayFromGameFile:
12965         (void) StopLoadGameTimer();
12966         if (gameFileFP != NULL) {
12967             gameFileFP = NULL;
12968         }
12969         break;
12970       case EditPosition:
12971         EditPositionDone(TRUE);
12972         break;
12973       case AnalyzeMode:
12974       case AnalyzeFile:
12975         ExitAnalyzeMode();
12976         SendToProgram("force\n", &first);
12977         break;
12978       case TwoMachinesPlay:
12979         GameEnds(EndOfFile, NULL, GE_PLAYER);
12980         ResurrectChessProgram();
12981         SetUserThinkingEnables();
12982         break;
12983       case EndOfGame:
12984         ResurrectChessProgram();
12985         break;
12986       case IcsPlayingBlack:
12987       case IcsPlayingWhite:
12988         DisplayError(_("Warning: You are still playing a game"), 0);
12989         break;
12990       case IcsObserving:
12991         DisplayError(_("Warning: You are still observing a game"), 0);
12992         break;
12993       case IcsExamining:
12994         DisplayError(_("Warning: You are still examining a game"), 0);
12995         break;
12996       case IcsIdle:
12997         break;
12998       case EditGame:
12999       default:
13000         return;
13001     }
13002
13003     pausing = FALSE;
13004     StopClocks();
13005     first.offeredDraw = second.offeredDraw = 0;
13006
13007     if (gameMode == PlayFromGameFile) {
13008         whiteTimeRemaining = timeRemaining[0][currentMove];
13009         blackTimeRemaining = timeRemaining[1][currentMove];
13010         DisplayTitle("");
13011     }
13012
13013     if (gameMode == MachinePlaysWhite ||
13014         gameMode == MachinePlaysBlack ||
13015         gameMode == TwoMachinesPlay ||
13016         gameMode == EndOfGame) {
13017         i = forwardMostMove;
13018         while (i > currentMove) {
13019             SendToProgram("undo\n", &first);
13020             i--;
13021         }
13022         whiteTimeRemaining = timeRemaining[0][currentMove];
13023         blackTimeRemaining = timeRemaining[1][currentMove];
13024         DisplayBothClocks();
13025         if (whiteFlag || blackFlag) {
13026             whiteFlag = blackFlag = 0;
13027         }
13028         DisplayTitle("");
13029     }
13030
13031     gameMode = EditGame;
13032     ModeHighlight();
13033     SetGameInfo();
13034 }
13035
13036
13037 void
13038 EditPositionEvent()
13039 {
13040     if (gameMode == EditPosition) {
13041         EditGameEvent();
13042         return;
13043     }
13044
13045     EditGameEvent();
13046     if (gameMode != EditGame) return;
13047
13048     gameMode = EditPosition;
13049     ModeHighlight();
13050     SetGameInfo();
13051     if (currentMove > 0)
13052       CopyBoard(boards[0], boards[currentMove]);
13053
13054     blackPlaysFirst = !WhiteOnMove(currentMove);
13055     ResetClocks();
13056     currentMove = forwardMostMove = backwardMostMove = 0;
13057     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13058     DisplayMove(-1);
13059 }
13060
13061 void
13062 ExitAnalyzeMode()
13063 {
13064     /* [DM] icsEngineAnalyze - possible call from other functions */
13065     if (appData.icsEngineAnalyze) {
13066         appData.icsEngineAnalyze = FALSE;
13067
13068         DisplayMessage("",_("Close ICS engine analyze..."));
13069     }
13070     if (first.analysisSupport && first.analyzing) {
13071       SendToProgram("exit\n", &first);
13072       first.analyzing = FALSE;
13073     }
13074     thinkOutput[0] = NULLCHAR;
13075 }
13076
13077 void
13078 EditPositionDone(Boolean fakeRights)
13079 {
13080     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13081
13082     startedFromSetupPosition = TRUE;
13083     InitChessProgram(&first, FALSE);
13084     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13085       boards[0][EP_STATUS] = EP_NONE;
13086       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13087     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13088         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13089         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13090       } else boards[0][CASTLING][2] = NoRights;
13091     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13092         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13093         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13094       } else boards[0][CASTLING][5] = NoRights;
13095     }
13096     SendToProgram("force\n", &first);
13097     if (blackPlaysFirst) {
13098         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13099         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13100         currentMove = forwardMostMove = backwardMostMove = 1;
13101         CopyBoard(boards[1], boards[0]);
13102     } else {
13103         currentMove = forwardMostMove = backwardMostMove = 0;
13104     }
13105     SendBoard(&first, forwardMostMove);
13106     if (appData.debugMode) {
13107         fprintf(debugFP, "EditPosDone\n");
13108     }
13109     DisplayTitle("");
13110     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13111     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13112     gameMode = EditGame;
13113     ModeHighlight();
13114     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13115     ClearHighlights(); /* [AS] */
13116 }
13117
13118 /* Pause for `ms' milliseconds */
13119 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13120 void
13121 TimeDelay(ms)
13122      long ms;
13123 {
13124     TimeMark m1, m2;
13125
13126     GetTimeMark(&m1);
13127     do {
13128         GetTimeMark(&m2);
13129     } while (SubtractTimeMarks(&m2, &m1) < ms);
13130 }
13131
13132 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13133 void
13134 SendMultiLineToICS(buf)
13135      char *buf;
13136 {
13137     char temp[MSG_SIZ+1], *p;
13138     int len;
13139
13140     len = strlen(buf);
13141     if (len > MSG_SIZ)
13142       len = MSG_SIZ;
13143
13144     strncpy(temp, buf, len);
13145     temp[len] = 0;
13146
13147     p = temp;
13148     while (*p) {
13149         if (*p == '\n' || *p == '\r')
13150           *p = ' ';
13151         ++p;
13152     }
13153
13154     strcat(temp, "\n");
13155     SendToICS(temp);
13156     SendToPlayer(temp, strlen(temp));
13157 }
13158
13159 void
13160 SetWhiteToPlayEvent()
13161 {
13162     if (gameMode == EditPosition) {
13163         blackPlaysFirst = FALSE;
13164         DisplayBothClocks();    /* works because currentMove is 0 */
13165     } else if (gameMode == IcsExamining) {
13166         SendToICS(ics_prefix);
13167         SendToICS("tomove white\n");
13168     }
13169 }
13170
13171 void
13172 SetBlackToPlayEvent()
13173 {
13174     if (gameMode == EditPosition) {
13175         blackPlaysFirst = TRUE;
13176         currentMove = 1;        /* kludge */
13177         DisplayBothClocks();
13178         currentMove = 0;
13179     } else if (gameMode == IcsExamining) {
13180         SendToICS(ics_prefix);
13181         SendToICS("tomove black\n");
13182     }
13183 }
13184
13185 void
13186 EditPositionMenuEvent(selection, x, y)
13187      ChessSquare selection;
13188      int x, y;
13189 {
13190     char buf[MSG_SIZ];
13191     ChessSquare piece = boards[0][y][x];
13192
13193     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13194
13195     switch (selection) {
13196       case ClearBoard:
13197         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13198             SendToICS(ics_prefix);
13199             SendToICS("bsetup clear\n");
13200         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13201             SendToICS(ics_prefix);
13202             SendToICS("clearboard\n");
13203         } else {
13204             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13205                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13206                 for (y = 0; y < BOARD_HEIGHT; y++) {
13207                     if (gameMode == IcsExamining) {
13208                         if (boards[currentMove][y][x] != EmptySquare) {
13209                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13210                                     AAA + x, ONE + y);
13211                             SendToICS(buf);
13212                         }
13213                     } else {
13214                         boards[0][y][x] = p;
13215                     }
13216                 }
13217             }
13218         }
13219         if (gameMode == EditPosition) {
13220             DrawPosition(FALSE, boards[0]);
13221         }
13222         break;
13223
13224       case WhitePlay:
13225         SetWhiteToPlayEvent();
13226         break;
13227
13228       case BlackPlay:
13229         SetBlackToPlayEvent();
13230         break;
13231
13232       case EmptySquare:
13233         if (gameMode == IcsExamining) {
13234             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13235             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13236             SendToICS(buf);
13237         } else {
13238             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13239                 if(x == BOARD_LEFT-2) {
13240                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13241                     boards[0][y][1] = 0;
13242                 } else
13243                 if(x == BOARD_RGHT+1) {
13244                     if(y >= gameInfo.holdingsSize) break;
13245                     boards[0][y][BOARD_WIDTH-2] = 0;
13246                 } else break;
13247             }
13248             boards[0][y][x] = EmptySquare;
13249             DrawPosition(FALSE, boards[0]);
13250         }
13251         break;
13252
13253       case PromotePiece:
13254         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13255            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13256             selection = (ChessSquare) (PROMOTED piece);
13257         } else if(piece == EmptySquare) selection = WhiteSilver;
13258         else selection = (ChessSquare)((int)piece - 1);
13259         goto defaultlabel;
13260
13261       case DemotePiece:
13262         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13263            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13264             selection = (ChessSquare) (DEMOTED piece);
13265         } else if(piece == EmptySquare) selection = BlackSilver;
13266         else selection = (ChessSquare)((int)piece + 1);
13267         goto defaultlabel;
13268
13269       case WhiteQueen:
13270       case BlackQueen:
13271         if(gameInfo.variant == VariantShatranj ||
13272            gameInfo.variant == VariantXiangqi  ||
13273            gameInfo.variant == VariantCourier  ||
13274            gameInfo.variant == VariantMakruk     )
13275             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13276         goto defaultlabel;
13277
13278       case WhiteKing:
13279       case BlackKing:
13280         if(gameInfo.variant == VariantXiangqi)
13281             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13282         if(gameInfo.variant == VariantKnightmate)
13283             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13284       default:
13285         defaultlabel:
13286         if (gameMode == IcsExamining) {
13287             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13288             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13289                      PieceToChar(selection), AAA + x, ONE + y);
13290             SendToICS(buf);
13291         } else {
13292             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13293                 int n;
13294                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13295                     n = PieceToNumber(selection - BlackPawn);
13296                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13297                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13298                     boards[0][BOARD_HEIGHT-1-n][1]++;
13299                 } else
13300                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13301                     n = PieceToNumber(selection);
13302                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13303                     boards[0][n][BOARD_WIDTH-1] = selection;
13304                     boards[0][n][BOARD_WIDTH-2]++;
13305                 }
13306             } else
13307             boards[0][y][x] = selection;
13308             DrawPosition(TRUE, boards[0]);
13309         }
13310         break;
13311     }
13312 }
13313
13314
13315 void
13316 DropMenuEvent(selection, x, y)
13317      ChessSquare selection;
13318      int x, y;
13319 {
13320     ChessMove moveType;
13321
13322     switch (gameMode) {
13323       case IcsPlayingWhite:
13324       case MachinePlaysBlack:
13325         if (!WhiteOnMove(currentMove)) {
13326             DisplayMoveError(_("It is Black's turn"));
13327             return;
13328         }
13329         moveType = WhiteDrop;
13330         break;
13331       case IcsPlayingBlack:
13332       case MachinePlaysWhite:
13333         if (WhiteOnMove(currentMove)) {
13334             DisplayMoveError(_("It is White's turn"));
13335             return;
13336         }
13337         moveType = BlackDrop;
13338         break;
13339       case EditGame:
13340         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13341         break;
13342       default:
13343         return;
13344     }
13345
13346     if (moveType == BlackDrop && selection < BlackPawn) {
13347       selection = (ChessSquare) ((int) selection
13348                                  + (int) BlackPawn - (int) WhitePawn);
13349     }
13350     if (boards[currentMove][y][x] != EmptySquare) {
13351         DisplayMoveError(_("That square is occupied"));
13352         return;
13353     }
13354
13355     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13356 }
13357
13358 void
13359 AcceptEvent()
13360 {
13361     /* Accept a pending offer of any kind from opponent */
13362
13363     if (appData.icsActive) {
13364         SendToICS(ics_prefix);
13365         SendToICS("accept\n");
13366     } else if (cmailMsgLoaded) {
13367         if (currentMove == cmailOldMove &&
13368             commentList[cmailOldMove] != NULL &&
13369             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13370                    "Black offers a draw" : "White offers a draw")) {
13371             TruncateGame();
13372             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13373             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13374         } else {
13375             DisplayError(_("There is no pending offer on this move"), 0);
13376             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13377         }
13378     } else {
13379         /* Not used for offers from chess program */
13380     }
13381 }
13382
13383 void
13384 DeclineEvent()
13385 {
13386     /* Decline a pending offer of any kind from opponent */
13387
13388     if (appData.icsActive) {
13389         SendToICS(ics_prefix);
13390         SendToICS("decline\n");
13391     } else if (cmailMsgLoaded) {
13392         if (currentMove == cmailOldMove &&
13393             commentList[cmailOldMove] != NULL &&
13394             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13395                    "Black offers a draw" : "White offers a draw")) {
13396 #ifdef NOTDEF
13397             AppendComment(cmailOldMove, "Draw declined", TRUE);
13398             DisplayComment(cmailOldMove - 1, "Draw declined");
13399 #endif /*NOTDEF*/
13400         } else {
13401             DisplayError(_("There is no pending offer on this move"), 0);
13402         }
13403     } else {
13404         /* Not used for offers from chess program */
13405     }
13406 }
13407
13408 void
13409 RematchEvent()
13410 {
13411     /* Issue ICS rematch command */
13412     if (appData.icsActive) {
13413         SendToICS(ics_prefix);
13414         SendToICS("rematch\n");
13415     }
13416 }
13417
13418 void
13419 CallFlagEvent()
13420 {
13421     /* Call your opponent's flag (claim a win on time) */
13422     if (appData.icsActive) {
13423         SendToICS(ics_prefix);
13424         SendToICS("flag\n");
13425     } else {
13426         switch (gameMode) {
13427           default:
13428             return;
13429           case MachinePlaysWhite:
13430             if (whiteFlag) {
13431                 if (blackFlag)
13432                   GameEnds(GameIsDrawn, "Both players ran out of time",
13433                            GE_PLAYER);
13434                 else
13435                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13436             } else {
13437                 DisplayError(_("Your opponent is not out of time"), 0);
13438             }
13439             break;
13440           case MachinePlaysBlack:
13441             if (blackFlag) {
13442                 if (whiteFlag)
13443                   GameEnds(GameIsDrawn, "Both players ran out of time",
13444                            GE_PLAYER);
13445                 else
13446                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13447             } else {
13448                 DisplayError(_("Your opponent is not out of time"), 0);
13449             }
13450             break;
13451         }
13452     }
13453 }
13454
13455 void
13456 ClockClick(int which)
13457 {       // [HGM] code moved to back-end from winboard.c
13458         if(which) { // black clock
13459           if (gameMode == EditPosition || gameMode == IcsExamining) {
13460             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13461             SetBlackToPlayEvent();
13462           } else if (gameMode == EditGame || shiftKey) {
13463             AdjustClock(which, -1);
13464           } else if (gameMode == IcsPlayingWhite ||
13465                      gameMode == MachinePlaysBlack) {
13466             CallFlagEvent();
13467           }
13468         } else { // white clock
13469           if (gameMode == EditPosition || gameMode == IcsExamining) {
13470             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13471             SetWhiteToPlayEvent();
13472           } else if (gameMode == EditGame || shiftKey) {
13473             AdjustClock(which, -1);
13474           } else if (gameMode == IcsPlayingBlack ||
13475                    gameMode == MachinePlaysWhite) {
13476             CallFlagEvent();
13477           }
13478         }
13479 }
13480
13481 void
13482 DrawEvent()
13483 {
13484     /* Offer draw or accept pending draw offer from opponent */
13485
13486     if (appData.icsActive) {
13487         /* Note: tournament rules require draw offers to be
13488            made after you make your move but before you punch
13489            your clock.  Currently ICS doesn't let you do that;
13490            instead, you immediately punch your clock after making
13491            a move, but you can offer a draw at any time. */
13492
13493         SendToICS(ics_prefix);
13494         SendToICS("draw\n");
13495         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13496     } else if (cmailMsgLoaded) {
13497         if (currentMove == cmailOldMove &&
13498             commentList[cmailOldMove] != NULL &&
13499             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13500                    "Black offers a draw" : "White offers a draw")) {
13501             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13502             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13503         } else if (currentMove == cmailOldMove + 1) {
13504             char *offer = WhiteOnMove(cmailOldMove) ?
13505               "White offers a draw" : "Black offers a draw";
13506             AppendComment(currentMove, offer, TRUE);
13507             DisplayComment(currentMove - 1, offer);
13508             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13509         } else {
13510             DisplayError(_("You must make your move before offering a draw"), 0);
13511             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13512         }
13513     } else if (first.offeredDraw) {
13514         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13515     } else {
13516         if (first.sendDrawOffers) {
13517             SendToProgram("draw\n", &first);
13518             userOfferedDraw = TRUE;
13519         }
13520     }
13521 }
13522
13523 void
13524 AdjournEvent()
13525 {
13526     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13527
13528     if (appData.icsActive) {
13529         SendToICS(ics_prefix);
13530         SendToICS("adjourn\n");
13531     } else {
13532         /* Currently GNU Chess doesn't offer or accept Adjourns */
13533     }
13534 }
13535
13536
13537 void
13538 AbortEvent()
13539 {
13540     /* Offer Abort or accept pending Abort offer from opponent */
13541
13542     if (appData.icsActive) {
13543         SendToICS(ics_prefix);
13544         SendToICS("abort\n");
13545     } else {
13546         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13547     }
13548 }
13549
13550 void
13551 ResignEvent()
13552 {
13553     /* Resign.  You can do this even if it's not your turn. */
13554
13555     if (appData.icsActive) {
13556         SendToICS(ics_prefix);
13557         SendToICS("resign\n");
13558     } else {
13559         switch (gameMode) {
13560           case MachinePlaysWhite:
13561             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13562             break;
13563           case MachinePlaysBlack:
13564             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13565             break;
13566           case EditGame:
13567             if (cmailMsgLoaded) {
13568                 TruncateGame();
13569                 if (WhiteOnMove(cmailOldMove)) {
13570                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13571                 } else {
13572                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13573                 }
13574                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13575             }
13576             break;
13577           default:
13578             break;
13579         }
13580     }
13581 }
13582
13583
13584 void
13585 StopObservingEvent()
13586 {
13587     /* Stop observing current games */
13588     SendToICS(ics_prefix);
13589     SendToICS("unobserve\n");
13590 }
13591
13592 void
13593 StopExaminingEvent()
13594 {
13595     /* Stop observing current game */
13596     SendToICS(ics_prefix);
13597     SendToICS("unexamine\n");
13598 }
13599
13600 void
13601 ForwardInner(target)
13602      int target;
13603 {
13604     int limit;
13605
13606     if (appData.debugMode)
13607         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13608                 target, currentMove, forwardMostMove);
13609
13610     if (gameMode == EditPosition)
13611       return;
13612
13613     if (gameMode == PlayFromGameFile && !pausing)
13614       PauseEvent();
13615
13616     if (gameMode == IcsExamining && pausing)
13617       limit = pauseExamForwardMostMove;
13618     else
13619       limit = forwardMostMove;
13620
13621     if (target > limit) target = limit;
13622
13623     if (target > 0 && moveList[target - 1][0]) {
13624         int fromX, fromY, toX, toY;
13625         toX = moveList[target - 1][2] - AAA;
13626         toY = moveList[target - 1][3] - ONE;
13627         if (moveList[target - 1][1] == '@') {
13628             if (appData.highlightLastMove) {
13629                 SetHighlights(-1, -1, toX, toY);
13630             }
13631         } else {
13632             fromX = moveList[target - 1][0] - AAA;
13633             fromY = moveList[target - 1][1] - ONE;
13634             if (target == currentMove + 1) {
13635                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13636             }
13637             if (appData.highlightLastMove) {
13638                 SetHighlights(fromX, fromY, toX, toY);
13639             }
13640         }
13641     }
13642     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13643         gameMode == Training || gameMode == PlayFromGameFile ||
13644         gameMode == AnalyzeFile) {
13645         while (currentMove < target) {
13646             SendMoveToProgram(currentMove++, &first);
13647         }
13648     } else {
13649         currentMove = target;
13650     }
13651
13652     if (gameMode == EditGame || gameMode == EndOfGame) {
13653         whiteTimeRemaining = timeRemaining[0][currentMove];
13654         blackTimeRemaining = timeRemaining[1][currentMove];
13655     }
13656     DisplayBothClocks();
13657     DisplayMove(currentMove - 1);
13658     DrawPosition(FALSE, boards[currentMove]);
13659     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13660     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13661         DisplayComment(currentMove - 1, commentList[currentMove]);
13662     }
13663     DisplayBook(currentMove);
13664 }
13665
13666
13667 void
13668 ForwardEvent()
13669 {
13670     if (gameMode == IcsExamining && !pausing) {
13671         SendToICS(ics_prefix);
13672         SendToICS("forward\n");
13673     } else {
13674         ForwardInner(currentMove + 1);
13675     }
13676 }
13677
13678 void
13679 ToEndEvent()
13680 {
13681     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13682         /* to optimze, we temporarily turn off analysis mode while we feed
13683          * the remaining moves to the engine. Otherwise we get analysis output
13684          * after each move.
13685          */
13686         if (first.analysisSupport) {
13687           SendToProgram("exit\nforce\n", &first);
13688           first.analyzing = FALSE;
13689         }
13690     }
13691
13692     if (gameMode == IcsExamining && !pausing) {
13693         SendToICS(ics_prefix);
13694         SendToICS("forward 999999\n");
13695     } else {
13696         ForwardInner(forwardMostMove);
13697     }
13698
13699     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13700         /* we have fed all the moves, so reactivate analysis mode */
13701         SendToProgram("analyze\n", &first);
13702         first.analyzing = TRUE;
13703         /*first.maybeThinking = TRUE;*/
13704         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13705     }
13706 }
13707
13708 void
13709 BackwardInner(target)
13710      int target;
13711 {
13712     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13713
13714     if (appData.debugMode)
13715         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13716                 target, currentMove, forwardMostMove);
13717
13718     if (gameMode == EditPosition) return;
13719     if (currentMove <= backwardMostMove) {
13720         ClearHighlights();
13721         DrawPosition(full_redraw, boards[currentMove]);
13722         return;
13723     }
13724     if (gameMode == PlayFromGameFile && !pausing)
13725       PauseEvent();
13726
13727     if (moveList[target][0]) {
13728         int fromX, fromY, toX, toY;
13729         toX = moveList[target][2] - AAA;
13730         toY = moveList[target][3] - ONE;
13731         if (moveList[target][1] == '@') {
13732             if (appData.highlightLastMove) {
13733                 SetHighlights(-1, -1, toX, toY);
13734             }
13735         } else {
13736             fromX = moveList[target][0] - AAA;
13737             fromY = moveList[target][1] - ONE;
13738             if (target == currentMove - 1) {
13739                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13740             }
13741             if (appData.highlightLastMove) {
13742                 SetHighlights(fromX, fromY, toX, toY);
13743             }
13744         }
13745     }
13746     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13747         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13748         while (currentMove > target) {
13749             SendToProgram("undo\n", &first);
13750             currentMove--;
13751         }
13752     } else {
13753         currentMove = target;
13754     }
13755
13756     if (gameMode == EditGame || gameMode == EndOfGame) {
13757         whiteTimeRemaining = timeRemaining[0][currentMove];
13758         blackTimeRemaining = timeRemaining[1][currentMove];
13759     }
13760     DisplayBothClocks();
13761     DisplayMove(currentMove - 1);
13762     DrawPosition(full_redraw, boards[currentMove]);
13763     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13764     // [HGM] PV info: routine tests if comment empty
13765     DisplayComment(currentMove - 1, commentList[currentMove]);
13766     DisplayBook(currentMove);
13767 }
13768
13769 void
13770 BackwardEvent()
13771 {
13772     if (gameMode == IcsExamining && !pausing) {
13773         SendToICS(ics_prefix);
13774         SendToICS("backward\n");
13775     } else {
13776         BackwardInner(currentMove - 1);
13777     }
13778 }
13779
13780 void
13781 ToStartEvent()
13782 {
13783     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13784         /* to optimize, we temporarily turn off analysis mode while we undo
13785          * all the moves. Otherwise we get analysis output after each undo.
13786          */
13787         if (first.analysisSupport) {
13788           SendToProgram("exit\nforce\n", &first);
13789           first.analyzing = FALSE;
13790         }
13791     }
13792
13793     if (gameMode == IcsExamining && !pausing) {
13794         SendToICS(ics_prefix);
13795         SendToICS("backward 999999\n");
13796     } else {
13797         BackwardInner(backwardMostMove);
13798     }
13799
13800     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13801         /* we have fed all the moves, so reactivate analysis mode */
13802         SendToProgram("analyze\n", &first);
13803         first.analyzing = TRUE;
13804         /*first.maybeThinking = TRUE;*/
13805         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13806     }
13807 }
13808
13809 void
13810 ToNrEvent(int to)
13811 {
13812   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13813   if (to >= forwardMostMove) to = forwardMostMove;
13814   if (to <= backwardMostMove) to = backwardMostMove;
13815   if (to < currentMove) {
13816     BackwardInner(to);
13817   } else {
13818     ForwardInner(to);
13819   }
13820 }
13821
13822 void
13823 RevertEvent(Boolean annotate)
13824 {
13825     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13826         return;
13827     }
13828     if (gameMode != IcsExamining) {
13829         DisplayError(_("You are not examining a game"), 0);
13830         return;
13831     }
13832     if (pausing) {
13833         DisplayError(_("You can't revert while pausing"), 0);
13834         return;
13835     }
13836     SendToICS(ics_prefix);
13837     SendToICS("revert\n");
13838 }
13839
13840 void
13841 RetractMoveEvent()
13842 {
13843     switch (gameMode) {
13844       case MachinePlaysWhite:
13845       case MachinePlaysBlack:
13846         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13847             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13848             return;
13849         }
13850         if (forwardMostMove < 2) return;
13851         currentMove = forwardMostMove = forwardMostMove - 2;
13852         whiteTimeRemaining = timeRemaining[0][currentMove];
13853         blackTimeRemaining = timeRemaining[1][currentMove];
13854         DisplayBothClocks();
13855         DisplayMove(currentMove - 1);
13856         ClearHighlights();/*!! could figure this out*/
13857         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13858         SendToProgram("remove\n", &first);
13859         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13860         break;
13861
13862       case BeginningOfGame:
13863       default:
13864         break;
13865
13866       case IcsPlayingWhite:
13867       case IcsPlayingBlack:
13868         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13869             SendToICS(ics_prefix);
13870             SendToICS("takeback 2\n");
13871         } else {
13872             SendToICS(ics_prefix);
13873             SendToICS("takeback 1\n");
13874         }
13875         break;
13876     }
13877 }
13878
13879 void
13880 MoveNowEvent()
13881 {
13882     ChessProgramState *cps;
13883
13884     switch (gameMode) {
13885       case MachinePlaysWhite:
13886         if (!WhiteOnMove(forwardMostMove)) {
13887             DisplayError(_("It is your turn"), 0);
13888             return;
13889         }
13890         cps = &first;
13891         break;
13892       case MachinePlaysBlack:
13893         if (WhiteOnMove(forwardMostMove)) {
13894             DisplayError(_("It is your turn"), 0);
13895             return;
13896         }
13897         cps = &first;
13898         break;
13899       case TwoMachinesPlay:
13900         if (WhiteOnMove(forwardMostMove) ==
13901             (first.twoMachinesColor[0] == 'w')) {
13902             cps = &first;
13903         } else {
13904             cps = &second;
13905         }
13906         break;
13907       case BeginningOfGame:
13908       default:
13909         return;
13910     }
13911     SendToProgram("?\n", cps);
13912 }
13913
13914 void
13915 TruncateGameEvent()
13916 {
13917     EditGameEvent();
13918     if (gameMode != EditGame) return;
13919     TruncateGame();
13920 }
13921
13922 void
13923 TruncateGame()
13924 {
13925     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13926     if (forwardMostMove > currentMove) {
13927         if (gameInfo.resultDetails != NULL) {
13928             free(gameInfo.resultDetails);
13929             gameInfo.resultDetails = NULL;
13930             gameInfo.result = GameUnfinished;
13931         }
13932         forwardMostMove = currentMove;
13933         HistorySet(parseList, backwardMostMove, forwardMostMove,
13934                    currentMove-1);
13935     }
13936 }
13937
13938 void
13939 HintEvent()
13940 {
13941     if (appData.noChessProgram) return;
13942     switch (gameMode) {
13943       case MachinePlaysWhite:
13944         if (WhiteOnMove(forwardMostMove)) {
13945             DisplayError(_("Wait until your turn"), 0);
13946             return;
13947         }
13948         break;
13949       case BeginningOfGame:
13950       case MachinePlaysBlack:
13951         if (!WhiteOnMove(forwardMostMove)) {
13952             DisplayError(_("Wait until your turn"), 0);
13953             return;
13954         }
13955         break;
13956       default:
13957         DisplayError(_("No hint available"), 0);
13958         return;
13959     }
13960     SendToProgram("hint\n", &first);
13961     hintRequested = TRUE;
13962 }
13963
13964 void
13965 BookEvent()
13966 {
13967     if (appData.noChessProgram) return;
13968     switch (gameMode) {
13969       case MachinePlaysWhite:
13970         if (WhiteOnMove(forwardMostMove)) {
13971             DisplayError(_("Wait until your turn"), 0);
13972             return;
13973         }
13974         break;
13975       case BeginningOfGame:
13976       case MachinePlaysBlack:
13977         if (!WhiteOnMove(forwardMostMove)) {
13978             DisplayError(_("Wait until your turn"), 0);
13979             return;
13980         }
13981         break;
13982       case EditPosition:
13983         EditPositionDone(TRUE);
13984         break;
13985       case TwoMachinesPlay:
13986         return;
13987       default:
13988         break;
13989     }
13990     SendToProgram("bk\n", &first);
13991     bookOutput[0] = NULLCHAR;
13992     bookRequested = TRUE;
13993 }
13994
13995 void
13996 AboutGameEvent()
13997 {
13998     char *tags = PGNTags(&gameInfo);
13999     TagsPopUp(tags, CmailMsg());
14000     free(tags);
14001 }
14002
14003 /* end button procedures */
14004
14005 void
14006 PrintPosition(fp, move)
14007      FILE *fp;
14008      int move;
14009 {
14010     int i, j;
14011
14012     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14013         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14014             char c = PieceToChar(boards[move][i][j]);
14015             fputc(c == 'x' ? '.' : c, fp);
14016             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14017         }
14018     }
14019     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14020       fprintf(fp, "white to play\n");
14021     else
14022       fprintf(fp, "black to play\n");
14023 }
14024
14025 void
14026 PrintOpponents(fp)
14027      FILE *fp;
14028 {
14029     if (gameInfo.white != NULL) {
14030         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14031     } else {
14032         fprintf(fp, "\n");
14033     }
14034 }
14035
14036 /* Find last component of program's own name, using some heuristics */
14037 void
14038 TidyProgramName(prog, host, buf)
14039      char *prog, *host, buf[MSG_SIZ];
14040 {
14041     char *p, *q;
14042     int local = (strcmp(host, "localhost") == 0);
14043     while (!local && (p = strchr(prog, ';')) != NULL) {
14044         p++;
14045         while (*p == ' ') p++;
14046         prog = p;
14047     }
14048     if (*prog == '"' || *prog == '\'') {
14049         q = strchr(prog + 1, *prog);
14050     } else {
14051         q = strchr(prog, ' ');
14052     }
14053     if (q == NULL) q = prog + strlen(prog);
14054     p = q;
14055     while (p >= prog && *p != '/' && *p != '\\') p--;
14056     p++;
14057     if(p == prog && *p == '"') p++;
14058     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14059     memcpy(buf, p, q - p);
14060     buf[q - p] = NULLCHAR;
14061     if (!local) {
14062         strcat(buf, "@");
14063         strcat(buf, host);
14064     }
14065 }
14066
14067 char *
14068 TimeControlTagValue()
14069 {
14070     char buf[MSG_SIZ];
14071     if (!appData.clockMode) {
14072       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14073     } else if (movesPerSession > 0) {
14074       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14075     } else if (timeIncrement == 0) {
14076       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14077     } else {
14078       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14079     }
14080     return StrSave(buf);
14081 }
14082
14083 void
14084 SetGameInfo()
14085 {
14086     /* This routine is used only for certain modes */
14087     VariantClass v = gameInfo.variant;
14088     ChessMove r = GameUnfinished;
14089     char *p = NULL;
14090
14091     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14092         r = gameInfo.result;
14093         p = gameInfo.resultDetails;
14094         gameInfo.resultDetails = NULL;
14095     }
14096     ClearGameInfo(&gameInfo);
14097     gameInfo.variant = v;
14098
14099     switch (gameMode) {
14100       case MachinePlaysWhite:
14101         gameInfo.event = StrSave( appData.pgnEventHeader );
14102         gameInfo.site = StrSave(HostName());
14103         gameInfo.date = PGNDate();
14104         gameInfo.round = StrSave("-");
14105         gameInfo.white = StrSave(first.tidy);
14106         gameInfo.black = StrSave(UserName());
14107         gameInfo.timeControl = TimeControlTagValue();
14108         break;
14109
14110       case MachinePlaysBlack:
14111         gameInfo.event = StrSave( appData.pgnEventHeader );
14112         gameInfo.site = StrSave(HostName());
14113         gameInfo.date = PGNDate();
14114         gameInfo.round = StrSave("-");
14115         gameInfo.white = StrSave(UserName());
14116         gameInfo.black = StrSave(first.tidy);
14117         gameInfo.timeControl = TimeControlTagValue();
14118         break;
14119
14120       case TwoMachinesPlay:
14121         gameInfo.event = StrSave( appData.pgnEventHeader );
14122         gameInfo.site = StrSave(HostName());
14123         gameInfo.date = PGNDate();
14124         if (roundNr > 0) {
14125             char buf[MSG_SIZ];
14126             snprintf(buf, MSG_SIZ, "%d", roundNr);
14127             gameInfo.round = StrSave(buf);
14128         } else {
14129             gameInfo.round = StrSave("-");
14130         }
14131         if (first.twoMachinesColor[0] == 'w') {
14132             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14133             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14134         } else {
14135             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14136             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14137         }
14138         gameInfo.timeControl = TimeControlTagValue();
14139         break;
14140
14141       case EditGame:
14142         gameInfo.event = StrSave("Edited game");
14143         gameInfo.site = StrSave(HostName());
14144         gameInfo.date = PGNDate();
14145         gameInfo.round = StrSave("-");
14146         gameInfo.white = StrSave("-");
14147         gameInfo.black = StrSave("-");
14148         gameInfo.result = r;
14149         gameInfo.resultDetails = p;
14150         break;
14151
14152       case EditPosition:
14153         gameInfo.event = StrSave("Edited position");
14154         gameInfo.site = StrSave(HostName());
14155         gameInfo.date = PGNDate();
14156         gameInfo.round = StrSave("-");
14157         gameInfo.white = StrSave("-");
14158         gameInfo.black = StrSave("-");
14159         break;
14160
14161       case IcsPlayingWhite:
14162       case IcsPlayingBlack:
14163       case IcsObserving:
14164       case IcsExamining:
14165         break;
14166
14167       case PlayFromGameFile:
14168         gameInfo.event = StrSave("Game from non-PGN file");
14169         gameInfo.site = StrSave(HostName());
14170         gameInfo.date = PGNDate();
14171         gameInfo.round = StrSave("-");
14172         gameInfo.white = StrSave("?");
14173         gameInfo.black = StrSave("?");
14174         break;
14175
14176       default:
14177         break;
14178     }
14179 }
14180
14181 void
14182 ReplaceComment(index, text)
14183      int index;
14184      char *text;
14185 {
14186     int len;
14187     char *p;
14188     float score;
14189
14190     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14191        pvInfoList[index-1].depth == len &&
14192        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14193        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14194     while (*text == '\n') text++;
14195     len = strlen(text);
14196     while (len > 0 && text[len - 1] == '\n') len--;
14197
14198     if (commentList[index] != NULL)
14199       free(commentList[index]);
14200
14201     if (len == 0) {
14202         commentList[index] = NULL;
14203         return;
14204     }
14205   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14206       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14207       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14208     commentList[index] = (char *) malloc(len + 2);
14209     strncpy(commentList[index], text, len);
14210     commentList[index][len] = '\n';
14211     commentList[index][len + 1] = NULLCHAR;
14212   } else {
14213     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14214     char *p;
14215     commentList[index] = (char *) malloc(len + 7);
14216     safeStrCpy(commentList[index], "{\n", 3);
14217     safeStrCpy(commentList[index]+2, text, len+1);
14218     commentList[index][len+2] = NULLCHAR;
14219     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14220     strcat(commentList[index], "\n}\n");
14221   }
14222 }
14223
14224 void
14225 CrushCRs(text)
14226      char *text;
14227 {
14228   char *p = text;
14229   char *q = text;
14230   char ch;
14231
14232   do {
14233     ch = *p++;
14234     if (ch == '\r') continue;
14235     *q++ = ch;
14236   } while (ch != '\0');
14237 }
14238
14239 void
14240 AppendComment(index, text, addBraces)
14241      int index;
14242      char *text;
14243      Boolean addBraces; // [HGM] braces: tells if we should add {}
14244 {
14245     int oldlen, len;
14246     char *old;
14247
14248 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14249     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14250
14251     CrushCRs(text);
14252     while (*text == '\n') text++;
14253     len = strlen(text);
14254     while (len > 0 && text[len - 1] == '\n') len--;
14255
14256     if (len == 0) return;
14257
14258     if (commentList[index] != NULL) {
14259         old = commentList[index];
14260         oldlen = strlen(old);
14261         while(commentList[index][oldlen-1] ==  '\n')
14262           commentList[index][--oldlen] = NULLCHAR;
14263         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14264         safeStrCpy(commentList[index], old, oldlen + len + 6);
14265         free(old);
14266         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14267         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14268           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14269           while (*text == '\n') { text++; len--; }
14270           commentList[index][--oldlen] = NULLCHAR;
14271       }
14272         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14273         else          strcat(commentList[index], "\n");
14274         strcat(commentList[index], text);
14275         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14276         else          strcat(commentList[index], "\n");
14277     } else {
14278         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14279         if(addBraces)
14280           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14281         else commentList[index][0] = NULLCHAR;
14282         strcat(commentList[index], text);
14283         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14284         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14285     }
14286 }
14287
14288 static char * FindStr( char * text, char * sub_text )
14289 {
14290     char * result = strstr( text, sub_text );
14291
14292     if( result != NULL ) {
14293         result += strlen( sub_text );
14294     }
14295
14296     return result;
14297 }
14298
14299 /* [AS] Try to extract PV info from PGN comment */
14300 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14301 char *GetInfoFromComment( int index, char * text )
14302 {
14303     char * sep = text, *p;
14304
14305     if( text != NULL && index > 0 ) {
14306         int score = 0;
14307         int depth = 0;
14308         int time = -1, sec = 0, deci;
14309         char * s_eval = FindStr( text, "[%eval " );
14310         char * s_emt = FindStr( text, "[%emt " );
14311
14312         if( s_eval != NULL || s_emt != NULL ) {
14313             /* New style */
14314             char delim;
14315
14316             if( s_eval != NULL ) {
14317                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14318                     return text;
14319                 }
14320
14321                 if( delim != ']' ) {
14322                     return text;
14323                 }
14324             }
14325
14326             if( s_emt != NULL ) {
14327             }
14328                 return text;
14329         }
14330         else {
14331             /* We expect something like: [+|-]nnn.nn/dd */
14332             int score_lo = 0;
14333
14334             if(*text != '{') return text; // [HGM] braces: must be normal comment
14335
14336             sep = strchr( text, '/' );
14337             if( sep == NULL || sep < (text+4) ) {
14338                 return text;
14339             }
14340
14341             p = text;
14342             if(p[1] == '(') { // comment starts with PV
14343                p = strchr(p, ')'); // locate end of PV
14344                if(p == NULL || sep < p+5) return text;
14345                // at this point we have something like "{(.*) +0.23/6 ..."
14346                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14347                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14348                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14349             }
14350             time = -1; sec = -1; deci = -1;
14351             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14352                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14353                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14354                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14355                 return text;
14356             }
14357
14358             if( score_lo < 0 || score_lo >= 100 ) {
14359                 return text;
14360             }
14361
14362             if(sec >= 0) time = 600*time + 10*sec; else
14363             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14364
14365             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14366
14367             /* [HGM] PV time: now locate end of PV info */
14368             while( *++sep >= '0' && *sep <= '9'); // strip depth
14369             if(time >= 0)
14370             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14371             if(sec >= 0)
14372             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14373             if(deci >= 0)
14374             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14375             while(*sep == ' ') sep++;
14376         }
14377
14378         if( depth <= 0 ) {
14379             return text;
14380         }
14381
14382         if( time < 0 ) {
14383             time = -1;
14384         }
14385
14386         pvInfoList[index-1].depth = depth;
14387         pvInfoList[index-1].score = score;
14388         pvInfoList[index-1].time  = 10*time; // centi-sec
14389         if(*sep == '}') *sep = 0; else *--sep = '{';
14390         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14391     }
14392     return sep;
14393 }
14394
14395 void
14396 SendToProgram(message, cps)
14397      char *message;
14398      ChessProgramState *cps;
14399 {
14400     int count, outCount, error;
14401     char buf[MSG_SIZ];
14402
14403     if (cps->pr == NULL) return;
14404     Attention(cps);
14405
14406     if (appData.debugMode) {
14407         TimeMark now;
14408         GetTimeMark(&now);
14409         fprintf(debugFP, "%ld >%-6s: %s",
14410                 SubtractTimeMarks(&now, &programStartTime),
14411                 cps->which, message);
14412     }
14413
14414     count = strlen(message);
14415     outCount = OutputToProcess(cps->pr, message, count, &error);
14416     if (outCount < count && !exiting
14417                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14418       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14419       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14420         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14421             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14422                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14423                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14424                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14425             } else {
14426                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14427                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14428                 gameInfo.result = res;
14429             }
14430             gameInfo.resultDetails = StrSave(buf);
14431         }
14432         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14433         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14434     }
14435 }
14436
14437 void
14438 ReceiveFromProgram(isr, closure, message, count, error)
14439      InputSourceRef isr;
14440      VOIDSTAR closure;
14441      char *message;
14442      int count;
14443      int error;
14444 {
14445     char *end_str;
14446     char buf[MSG_SIZ];
14447     ChessProgramState *cps = (ChessProgramState *)closure;
14448
14449     if (isr != cps->isr) return; /* Killed intentionally */
14450     if (count <= 0) {
14451         if (count == 0) {
14452             RemoveInputSource(cps->isr);
14453             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14454             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14455                     _(cps->which), cps->program);
14456         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14457                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14458                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14459                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14460                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14461                 } else {
14462                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14463                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14464                     gameInfo.result = res;
14465                 }
14466                 gameInfo.resultDetails = StrSave(buf);
14467             }
14468             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14469             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14470         } else {
14471             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14472                     _(cps->which), cps->program);
14473             RemoveInputSource(cps->isr);
14474
14475             /* [AS] Program is misbehaving badly... kill it */
14476             if( count == -2 ) {
14477                 DestroyChildProcess( cps->pr, 9 );
14478                 cps->pr = NoProc;
14479             }
14480
14481             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14482         }
14483         return;
14484     }
14485
14486     if ((end_str = strchr(message, '\r')) != NULL)
14487       *end_str = NULLCHAR;
14488     if ((end_str = strchr(message, '\n')) != NULL)
14489       *end_str = NULLCHAR;
14490
14491     if (appData.debugMode) {
14492         TimeMark now; int print = 1;
14493         char *quote = ""; char c; int i;
14494
14495         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14496                 char start = message[0];
14497                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14498                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14499                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14500                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14501                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14502                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14503                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14504                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14505                    sscanf(message, "hint: %c", &c)!=1 && 
14506                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14507                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14508                     print = (appData.engineComments >= 2);
14509                 }
14510                 message[0] = start; // restore original message
14511         }
14512         if(print) {
14513                 GetTimeMark(&now);
14514                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14515                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14516                         quote,
14517                         message);
14518         }
14519     }
14520
14521     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14522     if (appData.icsEngineAnalyze) {
14523         if (strstr(message, "whisper") != NULL ||
14524              strstr(message, "kibitz") != NULL ||
14525             strstr(message, "tellics") != NULL) return;
14526     }
14527
14528     HandleMachineMove(message, cps);
14529 }
14530
14531
14532 void
14533 SendTimeControl(cps, mps, tc, inc, sd, st)
14534      ChessProgramState *cps;
14535      int mps, inc, sd, st;
14536      long tc;
14537 {
14538     char buf[MSG_SIZ];
14539     int seconds;
14540
14541     if( timeControl_2 > 0 ) {
14542         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14543             tc = timeControl_2;
14544         }
14545     }
14546     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14547     inc /= cps->timeOdds;
14548     st  /= cps->timeOdds;
14549
14550     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14551
14552     if (st > 0) {
14553       /* Set exact time per move, normally using st command */
14554       if (cps->stKludge) {
14555         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14556         seconds = st % 60;
14557         if (seconds == 0) {
14558           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14559         } else {
14560           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14561         }
14562       } else {
14563         snprintf(buf, MSG_SIZ, "st %d\n", st);
14564       }
14565     } else {
14566       /* Set conventional or incremental time control, using level command */
14567       if (seconds == 0) {
14568         /* Note old gnuchess bug -- minutes:seconds used to not work.
14569            Fixed in later versions, but still avoid :seconds
14570            when seconds is 0. */
14571         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14572       } else {
14573         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14574                  seconds, inc/1000.);
14575       }
14576     }
14577     SendToProgram(buf, cps);
14578
14579     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14580     /* Orthogonally, limit search to given depth */
14581     if (sd > 0) {
14582       if (cps->sdKludge) {
14583         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14584       } else {
14585         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14586       }
14587       SendToProgram(buf, cps);
14588     }
14589
14590     if(cps->nps >= 0) { /* [HGM] nps */
14591         if(cps->supportsNPS == FALSE)
14592           cps->nps = -1; // don't use if engine explicitly says not supported!
14593         else {
14594           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14595           SendToProgram(buf, cps);
14596         }
14597     }
14598 }
14599
14600 ChessProgramState *WhitePlayer()
14601 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14602 {
14603     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14604        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14605         return &second;
14606     return &first;
14607 }
14608
14609 void
14610 SendTimeRemaining(cps, machineWhite)
14611      ChessProgramState *cps;
14612      int /*boolean*/ machineWhite;
14613 {
14614     char message[MSG_SIZ];
14615     long time, otime;
14616
14617     /* Note: this routine must be called when the clocks are stopped
14618        or when they have *just* been set or switched; otherwise
14619        it will be off by the time since the current tick started.
14620     */
14621     if (machineWhite) {
14622         time = whiteTimeRemaining / 10;
14623         otime = blackTimeRemaining / 10;
14624     } else {
14625         time = blackTimeRemaining / 10;
14626         otime = whiteTimeRemaining / 10;
14627     }
14628     /* [HGM] translate opponent's time by time-odds factor */
14629     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14630     if (appData.debugMode) {
14631         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14632     }
14633
14634     if (time <= 0) time = 1;
14635     if (otime <= 0) otime = 1;
14636
14637     snprintf(message, MSG_SIZ, "time %ld\n", time);
14638     SendToProgram(message, cps);
14639
14640     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14641     SendToProgram(message, cps);
14642 }
14643
14644 int
14645 BoolFeature(p, name, loc, cps)
14646      char **p;
14647      char *name;
14648      int *loc;
14649      ChessProgramState *cps;
14650 {
14651   char buf[MSG_SIZ];
14652   int len = strlen(name);
14653   int val;
14654
14655   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14656     (*p) += len + 1;
14657     sscanf(*p, "%d", &val);
14658     *loc = (val != 0);
14659     while (**p && **p != ' ')
14660       (*p)++;
14661     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14662     SendToProgram(buf, cps);
14663     return TRUE;
14664   }
14665   return FALSE;
14666 }
14667
14668 int
14669 IntFeature(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   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14678     (*p) += len + 1;
14679     sscanf(*p, "%d", loc);
14680     while (**p && **p != ' ') (*p)++;
14681     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14682     SendToProgram(buf, cps);
14683     return TRUE;
14684   }
14685   return FALSE;
14686 }
14687
14688 int
14689 StringFeature(p, name, loc, cps)
14690      char **p;
14691      char *name;
14692      char loc[];
14693      ChessProgramState *cps;
14694 {
14695   char buf[MSG_SIZ];
14696   int len = strlen(name);
14697   if (strncmp((*p), name, len) == 0
14698       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14699     (*p) += len + 2;
14700     sscanf(*p, "%[^\"]", loc);
14701     while (**p && **p != '\"') (*p)++;
14702     if (**p == '\"') (*p)++;
14703     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14704     SendToProgram(buf, cps);
14705     return TRUE;
14706   }
14707   return FALSE;
14708 }
14709
14710 int
14711 ParseOption(Option *opt, ChessProgramState *cps)
14712 // [HGM] options: process the string that defines an engine option, and determine
14713 // name, type, default value, and allowed value range
14714 {
14715         char *p, *q, buf[MSG_SIZ];
14716         int n, min = (-1)<<31, max = 1<<31, def;
14717
14718         if(p = strstr(opt->name, " -spin ")) {
14719             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14720             if(max < min) max = min; // enforce consistency
14721             if(def < min) def = min;
14722             if(def > max) def = max;
14723             opt->value = def;
14724             opt->min = min;
14725             opt->max = max;
14726             opt->type = Spin;
14727         } else if((p = strstr(opt->name, " -slider "))) {
14728             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14729             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14730             if(max < min) max = min; // enforce consistency
14731             if(def < min) def = min;
14732             if(def > max) def = max;
14733             opt->value = def;
14734             opt->min = min;
14735             opt->max = max;
14736             opt->type = Spin; // Slider;
14737         } else if((p = strstr(opt->name, " -string "))) {
14738             opt->textValue = p+9;
14739             opt->type = TextBox;
14740         } else if((p = strstr(opt->name, " -file "))) {
14741             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14742             opt->textValue = p+7;
14743             opt->type = FileName; // FileName;
14744         } else if((p = strstr(opt->name, " -path "))) {
14745             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14746             opt->textValue = p+7;
14747             opt->type = PathName; // PathName;
14748         } else if(p = strstr(opt->name, " -check ")) {
14749             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14750             opt->value = (def != 0);
14751             opt->type = CheckBox;
14752         } else if(p = strstr(opt->name, " -combo ")) {
14753             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14754             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14755             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14756             opt->value = n = 0;
14757             while(q = StrStr(q, " /// ")) {
14758                 n++; *q = 0;    // count choices, and null-terminate each of them
14759                 q += 5;
14760                 if(*q == '*') { // remember default, which is marked with * prefix
14761                     q++;
14762                     opt->value = n;
14763                 }
14764                 cps->comboList[cps->comboCnt++] = q;
14765             }
14766             cps->comboList[cps->comboCnt++] = NULL;
14767             opt->max = n + 1;
14768             opt->type = ComboBox;
14769         } else if(p = strstr(opt->name, " -button")) {
14770             opt->type = Button;
14771         } else if(p = strstr(opt->name, " -save")) {
14772             opt->type = SaveButton;
14773         } else return FALSE;
14774         *p = 0; // terminate option name
14775         // now look if the command-line options define a setting for this engine option.
14776         if(cps->optionSettings && cps->optionSettings[0])
14777             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14778         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14779           snprintf(buf, MSG_SIZ, "option %s", p);
14780                 if(p = strstr(buf, ",")) *p = 0;
14781                 if(q = strchr(buf, '=')) switch(opt->type) {
14782                     case ComboBox:
14783                         for(n=0; n<opt->max; n++)
14784                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14785                         break;
14786                     case TextBox:
14787                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14788                         break;
14789                     case Spin:
14790                     case CheckBox:
14791                         opt->value = atoi(q+1);
14792                     default:
14793                         break;
14794                 }
14795                 strcat(buf, "\n");
14796                 SendToProgram(buf, cps);
14797         }
14798         return TRUE;
14799 }
14800
14801 void
14802 FeatureDone(cps, val)
14803      ChessProgramState* cps;
14804      int val;
14805 {
14806   DelayedEventCallback cb = GetDelayedEvent();
14807   if ((cb == InitBackEnd3 && cps == &first) ||
14808       (cb == SettingsMenuIfReady && cps == &second) ||
14809       (cb == LoadEngine) ||
14810       (cb == TwoMachinesEventIfReady)) {
14811     CancelDelayedEvent();
14812     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14813   }
14814   cps->initDone = val;
14815 }
14816
14817 /* Parse feature command from engine */
14818 void
14819 ParseFeatures(args, cps)
14820      char* args;
14821      ChessProgramState *cps;
14822 {
14823   char *p = args;
14824   char *q;
14825   int val;
14826   char buf[MSG_SIZ];
14827
14828   for (;;) {
14829     while (*p == ' ') p++;
14830     if (*p == NULLCHAR) return;
14831
14832     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14833     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14834     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14835     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14836     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14837     if (BoolFeature(&p, "reuse", &val, cps)) {
14838       /* Engine can disable reuse, but can't enable it if user said no */
14839       if (!val) cps->reuse = FALSE;
14840       continue;
14841     }
14842     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14843     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14844       if (gameMode == TwoMachinesPlay) {
14845         DisplayTwoMachinesTitle();
14846       } else {
14847         DisplayTitle("");
14848       }
14849       continue;
14850     }
14851     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14852     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14853     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14854     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14855     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14856     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14857     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14858     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14859     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14860     if (IntFeature(&p, "done", &val, cps)) {
14861       FeatureDone(cps, val);
14862       continue;
14863     }
14864     /* Added by Tord: */
14865     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14866     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14867     /* End of additions by Tord */
14868
14869     /* [HGM] added features: */
14870     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14871     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14872     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14873     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14874     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14875     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14876     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14877         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14878           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14879             SendToProgram(buf, cps);
14880             continue;
14881         }
14882         if(cps->nrOptions >= MAX_OPTIONS) {
14883             cps->nrOptions--;
14884             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14885             DisplayError(buf, 0);
14886         }
14887         continue;
14888     }
14889     /* End of additions by HGM */
14890
14891     /* unknown feature: complain and skip */
14892     q = p;
14893     while (*q && *q != '=') q++;
14894     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14895     SendToProgram(buf, cps);
14896     p = q;
14897     if (*p == '=') {
14898       p++;
14899       if (*p == '\"') {
14900         p++;
14901         while (*p && *p != '\"') p++;
14902         if (*p == '\"') p++;
14903       } else {
14904         while (*p && *p != ' ') p++;
14905       }
14906     }
14907   }
14908
14909 }
14910
14911 void
14912 PeriodicUpdatesEvent(newState)
14913      int newState;
14914 {
14915     if (newState == appData.periodicUpdates)
14916       return;
14917
14918     appData.periodicUpdates=newState;
14919
14920     /* Display type changes, so update it now */
14921 //    DisplayAnalysis();
14922
14923     /* Get the ball rolling again... */
14924     if (newState) {
14925         AnalysisPeriodicEvent(1);
14926         StartAnalysisClock();
14927     }
14928 }
14929
14930 void
14931 PonderNextMoveEvent(newState)
14932      int newState;
14933 {
14934     if (newState == appData.ponderNextMove) return;
14935     if (gameMode == EditPosition) EditPositionDone(TRUE);
14936     if (newState) {
14937         SendToProgram("hard\n", &first);
14938         if (gameMode == TwoMachinesPlay) {
14939             SendToProgram("hard\n", &second);
14940         }
14941     } else {
14942         SendToProgram("easy\n", &first);
14943         thinkOutput[0] = NULLCHAR;
14944         if (gameMode == TwoMachinesPlay) {
14945             SendToProgram("easy\n", &second);
14946         }
14947     }
14948     appData.ponderNextMove = newState;
14949 }
14950
14951 void
14952 NewSettingEvent(option, feature, command, value)
14953      char *command;
14954      int option, value, *feature;
14955 {
14956     char buf[MSG_SIZ];
14957
14958     if (gameMode == EditPosition) EditPositionDone(TRUE);
14959     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14960     if(feature == NULL || *feature) SendToProgram(buf, &first);
14961     if (gameMode == TwoMachinesPlay) {
14962         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14963     }
14964 }
14965
14966 void
14967 ShowThinkingEvent()
14968 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14969 {
14970     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14971     int newState = appData.showThinking
14972         // [HGM] thinking: other features now need thinking output as well
14973         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14974
14975     if (oldState == newState) return;
14976     oldState = newState;
14977     if (gameMode == EditPosition) EditPositionDone(TRUE);
14978     if (oldState) {
14979         SendToProgram("post\n", &first);
14980         if (gameMode == TwoMachinesPlay) {
14981             SendToProgram("post\n", &second);
14982         }
14983     } else {
14984         SendToProgram("nopost\n", &first);
14985         thinkOutput[0] = NULLCHAR;
14986         if (gameMode == TwoMachinesPlay) {
14987             SendToProgram("nopost\n", &second);
14988         }
14989     }
14990 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14991 }
14992
14993 void
14994 AskQuestionEvent(title, question, replyPrefix, which)
14995      char *title; char *question; char *replyPrefix; char *which;
14996 {
14997   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14998   if (pr == NoProc) return;
14999   AskQuestion(title, question, replyPrefix, pr);
15000 }
15001
15002 void
15003 TypeInEvent(char firstChar)
15004 {
15005     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15006         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15007         gameMode == AnalyzeMode || gameMode == EditGame || \r
15008         gameMode == EditPosition || gameMode == IcsExamining ||\r
15009         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15010         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15011                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15012                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15013         gameMode == Training) PopUpMoveDialog(firstChar);
15014 }
15015
15016 void
15017 TypeInDoneEvent(char *move)
15018 {
15019         Board board;
15020         int n, fromX, fromY, toX, toY;
15021         char promoChar;
15022         ChessMove moveType;\r
15023
15024         // [HGM] FENedit\r
15025         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15026                 EditPositionPasteFEN(move);\r
15027                 return;\r
15028         }\r
15029         // [HGM] movenum: allow move number to be typed in any mode\r
15030         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15031           ToNrEvent(2*n-1);\r
15032           return;\r
15033         }\r
15034
15035       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15036         gameMode != Training) {\r
15037         DisplayMoveError(_("Displayed move is not current"));\r
15038       } else {\r
15039         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15040           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15041         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15042         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15043           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15044           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15045         } else {\r
15046           DisplayMoveError(_("Could not parse move"));\r
15047         }
15048       }\r
15049 }\r
15050
15051 void
15052 DisplayMove(moveNumber)
15053      int moveNumber;
15054 {
15055     char message[MSG_SIZ];
15056     char res[MSG_SIZ];
15057     char cpThinkOutput[MSG_SIZ];
15058
15059     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15060
15061     if (moveNumber == forwardMostMove - 1 ||
15062         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15063
15064         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15065
15066         if (strchr(cpThinkOutput, '\n')) {
15067             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15068         }
15069     } else {
15070         *cpThinkOutput = NULLCHAR;
15071     }
15072
15073     /* [AS] Hide thinking from human user */
15074     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15075         *cpThinkOutput = NULLCHAR;
15076         if( thinkOutput[0] != NULLCHAR ) {
15077             int i;
15078
15079             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15080                 cpThinkOutput[i] = '.';
15081             }
15082             cpThinkOutput[i] = NULLCHAR;
15083             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15084         }
15085     }
15086
15087     if (moveNumber == forwardMostMove - 1 &&
15088         gameInfo.resultDetails != NULL) {
15089         if (gameInfo.resultDetails[0] == NULLCHAR) {
15090           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15091         } else {
15092           snprintf(res, MSG_SIZ, " {%s} %s",
15093                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15094         }
15095     } else {
15096         res[0] = NULLCHAR;
15097     }
15098
15099     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15100         DisplayMessage(res, cpThinkOutput);
15101     } else {
15102       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15103                 WhiteOnMove(moveNumber) ? " " : ".. ",
15104                 parseList[moveNumber], res);
15105         DisplayMessage(message, cpThinkOutput);
15106     }
15107 }
15108
15109 void
15110 DisplayComment(moveNumber, text)
15111      int moveNumber;
15112      char *text;
15113 {
15114     char title[MSG_SIZ];
15115     char buf[8000]; // comment can be long!
15116     int score, depth;
15117
15118     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15119       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15120     } else {
15121       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15122               WhiteOnMove(moveNumber) ? " " : ".. ",
15123               parseList[moveNumber]);
15124     }
15125     // [HGM] PV info: display PV info together with (or as) comment
15126     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15127       if(text == NULL) text = "";
15128       score = pvInfoList[moveNumber].score;
15129       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15130               depth, (pvInfoList[moveNumber].time+50)/100, text);
15131       text = buf;
15132     }
15133     if (text != NULL && (appData.autoDisplayComment || commentUp))
15134         CommentPopUp(title, text);
15135 }
15136
15137 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15138  * might be busy thinking or pondering.  It can be omitted if your
15139  * gnuchess is configured to stop thinking immediately on any user
15140  * input.  However, that gnuchess feature depends on the FIONREAD
15141  * ioctl, which does not work properly on some flavors of Unix.
15142  */
15143 void
15144 Attention(cps)
15145      ChessProgramState *cps;
15146 {
15147 #if ATTENTION
15148     if (!cps->useSigint) return;
15149     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15150     switch (gameMode) {
15151       case MachinePlaysWhite:
15152       case MachinePlaysBlack:
15153       case TwoMachinesPlay:
15154       case IcsPlayingWhite:
15155       case IcsPlayingBlack:
15156       case AnalyzeMode:
15157       case AnalyzeFile:
15158         /* Skip if we know it isn't thinking */
15159         if (!cps->maybeThinking) return;
15160         if (appData.debugMode)
15161           fprintf(debugFP, "Interrupting %s\n", cps->which);
15162         InterruptChildProcess(cps->pr);
15163         cps->maybeThinking = FALSE;
15164         break;
15165       default:
15166         break;
15167     }
15168 #endif /*ATTENTION*/
15169 }
15170
15171 int
15172 CheckFlags()
15173 {
15174     if (whiteTimeRemaining <= 0) {
15175         if (!whiteFlag) {
15176             whiteFlag = TRUE;
15177             if (appData.icsActive) {
15178                 if (appData.autoCallFlag &&
15179                     gameMode == IcsPlayingBlack && !blackFlag) {
15180                   SendToICS(ics_prefix);
15181                   SendToICS("flag\n");
15182                 }
15183             } else {
15184                 if (blackFlag) {
15185                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15186                 } else {
15187                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15188                     if (appData.autoCallFlag) {
15189                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15190                         return TRUE;
15191                     }
15192                 }
15193             }
15194         }
15195     }
15196     if (blackTimeRemaining <= 0) {
15197         if (!blackFlag) {
15198             blackFlag = TRUE;
15199             if (appData.icsActive) {
15200                 if (appData.autoCallFlag &&
15201                     gameMode == IcsPlayingWhite && !whiteFlag) {
15202                   SendToICS(ics_prefix);
15203                   SendToICS("flag\n");
15204                 }
15205             } else {
15206                 if (whiteFlag) {
15207                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15208                 } else {
15209                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15210                     if (appData.autoCallFlag) {
15211                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15212                         return TRUE;
15213                     }
15214                 }
15215             }
15216         }
15217     }
15218     return FALSE;
15219 }
15220
15221 void
15222 CheckTimeControl()
15223 {
15224     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15225         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15226
15227     /*
15228      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15229      */
15230     if ( !WhiteOnMove(forwardMostMove) ) {
15231         /* White made time control */
15232         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15233         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15234         /* [HGM] time odds: correct new time quota for time odds! */
15235                                             / WhitePlayer()->timeOdds;
15236         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15237     } else {
15238         lastBlack -= blackTimeRemaining;
15239         /* Black made time control */
15240         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15241                                             / WhitePlayer()->other->timeOdds;
15242         lastWhite = whiteTimeRemaining;
15243     }
15244 }
15245
15246 void
15247 DisplayBothClocks()
15248 {
15249     int wom = gameMode == EditPosition ?
15250       !blackPlaysFirst : WhiteOnMove(currentMove);
15251     DisplayWhiteClock(whiteTimeRemaining, wom);
15252     DisplayBlackClock(blackTimeRemaining, !wom);
15253 }
15254
15255
15256 /* Timekeeping seems to be a portability nightmare.  I think everyone
15257    has ftime(), but I'm really not sure, so I'm including some ifdefs
15258    to use other calls if you don't.  Clocks will be less accurate if
15259    you have neither ftime nor gettimeofday.
15260 */
15261
15262 /* VS 2008 requires the #include outside of the function */
15263 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15264 #include <sys/timeb.h>
15265 #endif
15266
15267 /* Get the current time as a TimeMark */
15268 void
15269 GetTimeMark(tm)
15270      TimeMark *tm;
15271 {
15272 #if HAVE_GETTIMEOFDAY
15273
15274     struct timeval timeVal;
15275     struct timezone timeZone;
15276
15277     gettimeofday(&timeVal, &timeZone);
15278     tm->sec = (long) timeVal.tv_sec;
15279     tm->ms = (int) (timeVal.tv_usec / 1000L);
15280
15281 #else /*!HAVE_GETTIMEOFDAY*/
15282 #if HAVE_FTIME
15283
15284 // include <sys/timeb.h> / moved to just above start of function
15285     struct timeb timeB;
15286
15287     ftime(&timeB);
15288     tm->sec = (long) timeB.time;
15289     tm->ms = (int) timeB.millitm;
15290
15291 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15292     tm->sec = (long) time(NULL);
15293     tm->ms = 0;
15294 #endif
15295 #endif
15296 }
15297
15298 /* Return the difference in milliseconds between two
15299    time marks.  We assume the difference will fit in a long!
15300 */
15301 long
15302 SubtractTimeMarks(tm2, tm1)
15303      TimeMark *tm2, *tm1;
15304 {
15305     return 1000L*(tm2->sec - tm1->sec) +
15306            (long) (tm2->ms - tm1->ms);
15307 }
15308
15309
15310 /*
15311  * Code to manage the game clocks.
15312  *
15313  * In tournament play, black starts the clock and then white makes a move.
15314  * We give the human user a slight advantage if he is playing white---the
15315  * clocks don't run until he makes his first move, so it takes zero time.
15316  * Also, we don't account for network lag, so we could get out of sync
15317  * with GNU Chess's clock -- but then, referees are always right.
15318  */
15319
15320 static TimeMark tickStartTM;
15321 static long intendedTickLength;
15322
15323 long
15324 NextTickLength(timeRemaining)
15325      long timeRemaining;
15326 {
15327     long nominalTickLength, nextTickLength;
15328
15329     if (timeRemaining > 0L && timeRemaining <= 10000L)
15330       nominalTickLength = 100L;
15331     else
15332       nominalTickLength = 1000L;
15333     nextTickLength = timeRemaining % nominalTickLength;
15334     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15335
15336     return nextTickLength;
15337 }
15338
15339 /* Adjust clock one minute up or down */
15340 void
15341 AdjustClock(Boolean which, int dir)
15342 {
15343     if(which) blackTimeRemaining += 60000*dir;
15344     else      whiteTimeRemaining += 60000*dir;
15345     DisplayBothClocks();
15346 }
15347
15348 /* Stop clocks and reset to a fresh time control */
15349 void
15350 ResetClocks()
15351 {
15352     (void) StopClockTimer();
15353     if (appData.icsActive) {
15354         whiteTimeRemaining = blackTimeRemaining = 0;
15355     } else if (searchTime) {
15356         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15357         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15358     } else { /* [HGM] correct new time quote for time odds */
15359         whiteTC = blackTC = fullTimeControlString;
15360         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15361         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15362     }
15363     if (whiteFlag || blackFlag) {
15364         DisplayTitle("");
15365         whiteFlag = blackFlag = FALSE;
15366     }
15367     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15368     DisplayBothClocks();
15369 }
15370
15371 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15372
15373 /* Decrement running clock by amount of time that has passed */
15374 void
15375 DecrementClocks()
15376 {
15377     long timeRemaining;
15378     long lastTickLength, fudge;
15379     TimeMark now;
15380
15381     if (!appData.clockMode) return;
15382     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15383
15384     GetTimeMark(&now);
15385
15386     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15387
15388     /* Fudge if we woke up a little too soon */
15389     fudge = intendedTickLength - lastTickLength;
15390     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15391
15392     if (WhiteOnMove(forwardMostMove)) {
15393         if(whiteNPS >= 0) lastTickLength = 0;
15394         timeRemaining = whiteTimeRemaining -= lastTickLength;
15395         if(timeRemaining < 0 && !appData.icsActive) {
15396             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15397             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15398                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15399                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15400             }
15401         }
15402         DisplayWhiteClock(whiteTimeRemaining - fudge,
15403                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15404     } else {
15405         if(blackNPS >= 0) lastTickLength = 0;
15406         timeRemaining = blackTimeRemaining -= lastTickLength;
15407         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15408             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15409             if(suddenDeath) {
15410                 blackStartMove = forwardMostMove;
15411                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15412             }
15413         }
15414         DisplayBlackClock(blackTimeRemaining - fudge,
15415                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15416     }
15417     if (CheckFlags()) return;
15418
15419     tickStartTM = now;
15420     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15421     StartClockTimer(intendedTickLength);
15422
15423     /* if the time remaining has fallen below the alarm threshold, sound the
15424      * alarm. if the alarm has sounded and (due to a takeback or time control
15425      * with increment) the time remaining has increased to a level above the
15426      * threshold, reset the alarm so it can sound again.
15427      */
15428
15429     if (appData.icsActive && appData.icsAlarm) {
15430
15431         /* make sure we are dealing with the user's clock */
15432         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15433                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15434            )) return;
15435
15436         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15437             alarmSounded = FALSE;
15438         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15439             PlayAlarmSound();
15440             alarmSounded = TRUE;
15441         }
15442     }
15443 }
15444
15445
15446 /* A player has just moved, so stop the previously running
15447    clock and (if in clock mode) start the other one.
15448    We redisplay both clocks in case we're in ICS mode, because
15449    ICS gives us an update to both clocks after every move.
15450    Note that this routine is called *after* forwardMostMove
15451    is updated, so the last fractional tick must be subtracted
15452    from the color that is *not* on move now.
15453 */
15454 void
15455 SwitchClocks(int newMoveNr)
15456 {
15457     long lastTickLength;
15458     TimeMark now;
15459     int flagged = FALSE;
15460
15461     GetTimeMark(&now);
15462
15463     if (StopClockTimer() && appData.clockMode) {
15464         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15465         if (!WhiteOnMove(forwardMostMove)) {
15466             if(blackNPS >= 0) lastTickLength = 0;
15467             blackTimeRemaining -= lastTickLength;
15468            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15469 //         if(pvInfoList[forwardMostMove].time == -1)
15470                  pvInfoList[forwardMostMove].time =               // use GUI time
15471                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15472         } else {
15473            if(whiteNPS >= 0) lastTickLength = 0;
15474            whiteTimeRemaining -= lastTickLength;
15475            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15476 //         if(pvInfoList[forwardMostMove].time == -1)
15477                  pvInfoList[forwardMostMove].time =
15478                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15479         }
15480         flagged = CheckFlags();
15481     }
15482     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15483     CheckTimeControl();
15484
15485     if (flagged || !appData.clockMode) return;
15486
15487     switch (gameMode) {
15488       case MachinePlaysBlack:
15489       case MachinePlaysWhite:
15490       case BeginningOfGame:
15491         if (pausing) return;
15492         break;
15493
15494       case EditGame:
15495       case PlayFromGameFile:
15496       case IcsExamining:
15497         return;
15498
15499       default:
15500         break;
15501     }
15502
15503     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15504         if(WhiteOnMove(forwardMostMove))
15505              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15506         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15507     }
15508
15509     tickStartTM = now;
15510     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15511       whiteTimeRemaining : blackTimeRemaining);
15512     StartClockTimer(intendedTickLength);
15513 }
15514
15515
15516 /* Stop both clocks */
15517 void
15518 StopClocks()
15519 {
15520     long lastTickLength;
15521     TimeMark now;
15522
15523     if (!StopClockTimer()) return;
15524     if (!appData.clockMode) return;
15525
15526     GetTimeMark(&now);
15527
15528     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15529     if (WhiteOnMove(forwardMostMove)) {
15530         if(whiteNPS >= 0) lastTickLength = 0;
15531         whiteTimeRemaining -= lastTickLength;
15532         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15533     } else {
15534         if(blackNPS >= 0) lastTickLength = 0;
15535         blackTimeRemaining -= lastTickLength;
15536         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15537     }
15538     CheckFlags();
15539 }
15540
15541 /* Start clock of player on move.  Time may have been reset, so
15542    if clock is already running, stop and restart it. */
15543 void
15544 StartClocks()
15545 {
15546     (void) StopClockTimer(); /* in case it was running already */
15547     DisplayBothClocks();
15548     if (CheckFlags()) return;
15549
15550     if (!appData.clockMode) return;
15551     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15552
15553     GetTimeMark(&tickStartTM);
15554     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15555       whiteTimeRemaining : blackTimeRemaining);
15556
15557    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15558     whiteNPS = blackNPS = -1;
15559     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15560        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15561         whiteNPS = first.nps;
15562     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15563        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15564         blackNPS = first.nps;
15565     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15566         whiteNPS = second.nps;
15567     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15568         blackNPS = second.nps;
15569     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15570
15571     StartClockTimer(intendedTickLength);
15572 }
15573
15574 char *
15575 TimeString(ms)
15576      long ms;
15577 {
15578     long second, minute, hour, day;
15579     char *sign = "";
15580     static char buf[32];
15581
15582     if (ms > 0 && ms <= 9900) {
15583       /* convert milliseconds to tenths, rounding up */
15584       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15585
15586       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15587       return buf;
15588     }
15589
15590     /* convert milliseconds to seconds, rounding up */
15591     /* use floating point to avoid strangeness of integer division
15592        with negative dividends on many machines */
15593     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15594
15595     if (second < 0) {
15596         sign = "-";
15597         second = -second;
15598     }
15599
15600     day = second / (60 * 60 * 24);
15601     second = second % (60 * 60 * 24);
15602     hour = second / (60 * 60);
15603     second = second % (60 * 60);
15604     minute = second / 60;
15605     second = second % 60;
15606
15607     if (day > 0)
15608       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15609               sign, day, hour, minute, second);
15610     else if (hour > 0)
15611       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15612     else
15613       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15614
15615     return buf;
15616 }
15617
15618
15619 /*
15620  * This is necessary because some C libraries aren't ANSI C compliant yet.
15621  */
15622 char *
15623 StrStr(string, match)
15624      char *string, *match;
15625 {
15626     int i, length;
15627
15628     length = strlen(match);
15629
15630     for (i = strlen(string) - length; i >= 0; i--, string++)
15631       if (!strncmp(match, string, length))
15632         return string;
15633
15634     return NULL;
15635 }
15636
15637 char *
15638 StrCaseStr(string, match)
15639      char *string, *match;
15640 {
15641     int i, j, length;
15642
15643     length = strlen(match);
15644
15645     for (i = strlen(string) - length; i >= 0; i--, string++) {
15646         for (j = 0; j < length; j++) {
15647             if (ToLower(match[j]) != ToLower(string[j]))
15648               break;
15649         }
15650         if (j == length) return string;
15651     }
15652
15653     return NULL;
15654 }
15655
15656 #ifndef _amigados
15657 int
15658 StrCaseCmp(s1, s2)
15659      char *s1, *s2;
15660 {
15661     char c1, c2;
15662
15663     for (;;) {
15664         c1 = ToLower(*s1++);
15665         c2 = ToLower(*s2++);
15666         if (c1 > c2) return 1;
15667         if (c1 < c2) return -1;
15668         if (c1 == NULLCHAR) return 0;
15669     }
15670 }
15671
15672
15673 int
15674 ToLower(c)
15675      int c;
15676 {
15677     return isupper(c) ? tolower(c) : c;
15678 }
15679
15680
15681 int
15682 ToUpper(c)
15683      int c;
15684 {
15685     return islower(c) ? toupper(c) : c;
15686 }
15687 #endif /* !_amigados    */
15688
15689 char *
15690 StrSave(s)
15691      char *s;
15692 {
15693   char *ret;
15694
15695   if ((ret = (char *) malloc(strlen(s) + 1)))
15696     {
15697       safeStrCpy(ret, s, strlen(s)+1);
15698     }
15699   return ret;
15700 }
15701
15702 char *
15703 StrSavePtr(s, savePtr)
15704      char *s, **savePtr;
15705 {
15706     if (*savePtr) {
15707         free(*savePtr);
15708     }
15709     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15710       safeStrCpy(*savePtr, s, strlen(s)+1);
15711     }
15712     return(*savePtr);
15713 }
15714
15715 char *
15716 PGNDate()
15717 {
15718     time_t clock;
15719     struct tm *tm;
15720     char buf[MSG_SIZ];
15721
15722     clock = time((time_t *)NULL);
15723     tm = localtime(&clock);
15724     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15725             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15726     return StrSave(buf);
15727 }
15728
15729
15730 char *
15731 PositionToFEN(move, overrideCastling)
15732      int move;
15733      char *overrideCastling;
15734 {
15735     int i, j, fromX, fromY, toX, toY;
15736     int whiteToPlay;
15737     char buf[128];
15738     char *p, *q;
15739     int emptycount;
15740     ChessSquare piece;
15741
15742     whiteToPlay = (gameMode == EditPosition) ?
15743       !blackPlaysFirst : (move % 2 == 0);
15744     p = buf;
15745
15746     /* Piece placement data */
15747     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15748         emptycount = 0;
15749         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15750             if (boards[move][i][j] == EmptySquare) {
15751                 emptycount++;
15752             } else { ChessSquare piece = boards[move][i][j];
15753                 if (emptycount > 0) {
15754                     if(emptycount<10) /* [HGM] can be >= 10 */
15755                         *p++ = '0' + emptycount;
15756                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15757                     emptycount = 0;
15758                 }
15759                 if(PieceToChar(piece) == '+') {
15760                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15761                     *p++ = '+';
15762                     piece = (ChessSquare)(DEMOTED piece);
15763                 }
15764                 *p++ = PieceToChar(piece);
15765                 if(p[-1] == '~') {
15766                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15767                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15768                     *p++ = '~';
15769                 }
15770             }
15771         }
15772         if (emptycount > 0) {
15773             if(emptycount<10) /* [HGM] can be >= 10 */
15774                 *p++ = '0' + emptycount;
15775             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15776             emptycount = 0;
15777         }
15778         *p++ = '/';
15779     }
15780     *(p - 1) = ' ';
15781
15782     /* [HGM] print Crazyhouse or Shogi holdings */
15783     if( gameInfo.holdingsWidth ) {
15784         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15785         q = p;
15786         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15787             piece = boards[move][i][BOARD_WIDTH-1];
15788             if( piece != EmptySquare )
15789               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15790                   *p++ = PieceToChar(piece);
15791         }
15792         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15793             piece = boards[move][BOARD_HEIGHT-i-1][0];
15794             if( piece != EmptySquare )
15795               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15796                   *p++ = PieceToChar(piece);
15797         }
15798
15799         if( q == p ) *p++ = '-';
15800         *p++ = ']';
15801         *p++ = ' ';
15802     }
15803
15804     /* Active color */
15805     *p++ = whiteToPlay ? 'w' : 'b';
15806     *p++ = ' ';
15807
15808   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15809     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15810   } else {
15811   if(nrCastlingRights) {
15812      q = p;
15813      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15814        /* [HGM] write directly from rights */
15815            if(boards[move][CASTLING][2] != NoRights &&
15816               boards[move][CASTLING][0] != NoRights   )
15817                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15818            if(boards[move][CASTLING][2] != NoRights &&
15819               boards[move][CASTLING][1] != NoRights   )
15820                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15821            if(boards[move][CASTLING][5] != NoRights &&
15822               boards[move][CASTLING][3] != NoRights   )
15823                 *p++ = boards[move][CASTLING][3] + AAA;
15824            if(boards[move][CASTLING][5] != NoRights &&
15825               boards[move][CASTLING][4] != NoRights   )
15826                 *p++ = boards[move][CASTLING][4] + AAA;
15827      } else {
15828
15829         /* [HGM] write true castling rights */
15830         if( nrCastlingRights == 6 ) {
15831             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15832                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15833             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15834                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15835             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15836                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15837             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15838                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15839         }
15840      }
15841      if (q == p) *p++ = '-'; /* No castling rights */
15842      *p++ = ' ';
15843   }
15844
15845   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15846      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15847     /* En passant target square */
15848     if (move > backwardMostMove) {
15849         fromX = moveList[move - 1][0] - AAA;
15850         fromY = moveList[move - 1][1] - ONE;
15851         toX = moveList[move - 1][2] - AAA;
15852         toY = moveList[move - 1][3] - ONE;
15853         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15854             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15855             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15856             fromX == toX) {
15857             /* 2-square pawn move just happened */
15858             *p++ = toX + AAA;
15859             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15860         } else {
15861             *p++ = '-';
15862         }
15863     } else if(move == backwardMostMove) {
15864         // [HGM] perhaps we should always do it like this, and forget the above?
15865         if((signed char)boards[move][EP_STATUS] >= 0) {
15866             *p++ = boards[move][EP_STATUS] + AAA;
15867             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15868         } else {
15869             *p++ = '-';
15870         }
15871     } else {
15872         *p++ = '-';
15873     }
15874     *p++ = ' ';
15875   }
15876   }
15877
15878     /* [HGM] find reversible plies */
15879     {   int i = 0, j=move;
15880
15881         if (appData.debugMode) { int k;
15882             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15883             for(k=backwardMostMove; k<=forwardMostMove; k++)
15884                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15885
15886         }
15887
15888         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15889         if( j == backwardMostMove ) i += initialRulePlies;
15890         sprintf(p, "%d ", i);
15891         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15892     }
15893     /* Fullmove number */
15894     sprintf(p, "%d", (move / 2) + 1);
15895
15896     return StrSave(buf);
15897 }
15898
15899 Boolean
15900 ParseFEN(board, blackPlaysFirst, fen)
15901     Board board;
15902      int *blackPlaysFirst;
15903      char *fen;
15904 {
15905     int i, j;
15906     char *p, c;
15907     int emptycount;
15908     ChessSquare piece;
15909
15910     p = fen;
15911
15912     /* [HGM] by default clear Crazyhouse holdings, if present */
15913     if(gameInfo.holdingsWidth) {
15914        for(i=0; i<BOARD_HEIGHT; i++) {
15915            board[i][0]             = EmptySquare; /* black holdings */
15916            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15917            board[i][1]             = (ChessSquare) 0; /* black counts */
15918            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15919        }
15920     }
15921
15922     /* Piece placement data */
15923     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15924         j = 0;
15925         for (;;) {
15926             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15927                 if (*p == '/') p++;
15928                 emptycount = gameInfo.boardWidth - j;
15929                 while (emptycount--)
15930                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15931                 break;
15932 #if(BOARD_FILES >= 10)
15933             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15934                 p++; emptycount=10;
15935                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15936                 while (emptycount--)
15937                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15938 #endif
15939             } else if (isdigit(*p)) {
15940                 emptycount = *p++ - '0';
15941                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15942                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15943                 while (emptycount--)
15944                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15945             } else if (*p == '+' || isalpha(*p)) {
15946                 if (j >= gameInfo.boardWidth) return FALSE;
15947                 if(*p=='+') {
15948                     piece = CharToPiece(*++p);
15949                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15950                     piece = (ChessSquare) (PROMOTED piece ); p++;
15951                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15952                 } else piece = CharToPiece(*p++);
15953
15954                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15955                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15956                     piece = (ChessSquare) (PROMOTED piece);
15957                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15958                     p++;
15959                 }
15960                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15961             } else {
15962                 return FALSE;
15963             }
15964         }
15965     }
15966     while (*p == '/' || *p == ' ') p++;
15967
15968     /* [HGM] look for Crazyhouse holdings here */
15969     while(*p==' ') p++;
15970     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15971         if(*p == '[') p++;
15972         if(*p == '-' ) p++; /* empty holdings */ else {
15973             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15974             /* if we would allow FEN reading to set board size, we would   */
15975             /* have to add holdings and shift the board read so far here   */
15976             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15977                 p++;
15978                 if((int) piece >= (int) BlackPawn ) {
15979                     i = (int)piece - (int)BlackPawn;
15980                     i = PieceToNumber((ChessSquare)i);
15981                     if( i >= gameInfo.holdingsSize ) return FALSE;
15982                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15983                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15984                 } else {
15985                     i = (int)piece - (int)WhitePawn;
15986                     i = PieceToNumber((ChessSquare)i);
15987                     if( i >= gameInfo.holdingsSize ) return FALSE;
15988                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15989                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15990                 }
15991             }
15992         }
15993         if(*p == ']') p++;
15994     }
15995
15996     while(*p == ' ') p++;
15997
15998     /* Active color */
15999     c = *p++;
16000     if(appData.colorNickNames) {
16001       if( c == appData.colorNickNames[0] ) c = 'w'; else
16002       if( c == appData.colorNickNames[1] ) c = 'b';
16003     }
16004     switch (c) {
16005       case 'w':
16006         *blackPlaysFirst = FALSE;
16007         break;
16008       case 'b':
16009         *blackPlaysFirst = TRUE;
16010         break;
16011       default:
16012         return FALSE;
16013     }
16014
16015     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16016     /* return the extra info in global variiables             */
16017
16018     /* set defaults in case FEN is incomplete */
16019     board[EP_STATUS] = EP_UNKNOWN;
16020     for(i=0; i<nrCastlingRights; i++ ) {
16021         board[CASTLING][i] =
16022             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16023     }   /* assume possible unless obviously impossible */
16024     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16025     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16026     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16027                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16028     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16029     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16030     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16031                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16032     FENrulePlies = 0;
16033
16034     while(*p==' ') p++;
16035     if(nrCastlingRights) {
16036       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16037           /* castling indicator present, so default becomes no castlings */
16038           for(i=0; i<nrCastlingRights; i++ ) {
16039                  board[CASTLING][i] = NoRights;
16040           }
16041       }
16042       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16043              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16044              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16045              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16046         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16047
16048         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16049             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16050             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16051         }
16052         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16053             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16054         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16055                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16056         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16057                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16058         switch(c) {
16059           case'K':
16060               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16061               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16062               board[CASTLING][2] = whiteKingFile;
16063               break;
16064           case'Q':
16065               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16066               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16067               board[CASTLING][2] = whiteKingFile;
16068               break;
16069           case'k':
16070               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16071               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16072               board[CASTLING][5] = blackKingFile;
16073               break;
16074           case'q':
16075               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16076               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16077               board[CASTLING][5] = blackKingFile;
16078           case '-':
16079               break;
16080           default: /* FRC castlings */
16081               if(c >= 'a') { /* black rights */
16082                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16083                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16084                   if(i == BOARD_RGHT) break;
16085                   board[CASTLING][5] = i;
16086                   c -= AAA;
16087                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16088                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16089                   if(c > i)
16090                       board[CASTLING][3] = c;
16091                   else
16092                       board[CASTLING][4] = c;
16093               } else { /* white rights */
16094                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16095                     if(board[0][i] == WhiteKing) break;
16096                   if(i == BOARD_RGHT) break;
16097                   board[CASTLING][2] = i;
16098                   c -= AAA - 'a' + 'A';
16099                   if(board[0][c] >= WhiteKing) break;
16100                   if(c > i)
16101                       board[CASTLING][0] = c;
16102                   else
16103                       board[CASTLING][1] = c;
16104               }
16105         }
16106       }
16107       for(i=0; i<nrCastlingRights; i++)
16108         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16109     if (appData.debugMode) {
16110         fprintf(debugFP, "FEN castling rights:");
16111         for(i=0; i<nrCastlingRights; i++)
16112         fprintf(debugFP, " %d", board[CASTLING][i]);
16113         fprintf(debugFP, "\n");
16114     }
16115
16116       while(*p==' ') p++;
16117     }
16118
16119     /* read e.p. field in games that know e.p. capture */
16120     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16121        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16122       if(*p=='-') {
16123         p++; board[EP_STATUS] = EP_NONE;
16124       } else {
16125          char c = *p++ - AAA;
16126
16127          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16128          if(*p >= '0' && *p <='9') p++;
16129          board[EP_STATUS] = c;
16130       }
16131     }
16132
16133
16134     if(sscanf(p, "%d", &i) == 1) {
16135         FENrulePlies = i; /* 50-move ply counter */
16136         /* (The move number is still ignored)    */
16137     }
16138
16139     return TRUE;
16140 }
16141
16142 void
16143 EditPositionPasteFEN(char *fen)
16144 {
16145   if (fen != NULL) {
16146     Board initial_position;
16147
16148     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16149       DisplayError(_("Bad FEN position in clipboard"), 0);
16150       return ;
16151     } else {
16152       int savedBlackPlaysFirst = blackPlaysFirst;
16153       EditPositionEvent();
16154       blackPlaysFirst = savedBlackPlaysFirst;
16155       CopyBoard(boards[0], initial_position);
16156       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16157       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16158       DisplayBothClocks();
16159       DrawPosition(FALSE, boards[currentMove]);
16160     }
16161   }
16162 }
16163
16164 static char cseq[12] = "\\   ";
16165
16166 Boolean set_cont_sequence(char *new_seq)
16167 {
16168     int len;
16169     Boolean ret;
16170
16171     // handle bad attempts to set the sequence
16172         if (!new_seq)
16173                 return 0; // acceptable error - no debug
16174
16175     len = strlen(new_seq);
16176     ret = (len > 0) && (len < sizeof(cseq));
16177     if (ret)
16178       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16179     else if (appData.debugMode)
16180       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16181     return ret;
16182 }
16183
16184 /*
16185     reformat a source message so words don't cross the width boundary.  internal
16186     newlines are not removed.  returns the wrapped size (no null character unless
16187     included in source message).  If dest is NULL, only calculate the size required
16188     for the dest buffer.  lp argument indicats line position upon entry, and it's
16189     passed back upon exit.
16190 */
16191 int wrap(char *dest, char *src, int count, int width, int *lp)
16192 {
16193     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16194
16195     cseq_len = strlen(cseq);
16196     old_line = line = *lp;
16197     ansi = len = clen = 0;
16198
16199     for (i=0; i < count; i++)
16200     {
16201         if (src[i] == '\033')
16202             ansi = 1;
16203
16204         // if we hit the width, back up
16205         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16206         {
16207             // store i & len in case the word is too long
16208             old_i = i, old_len = len;
16209
16210             // find the end of the last word
16211             while (i && src[i] != ' ' && src[i] != '\n')
16212             {
16213                 i--;
16214                 len--;
16215             }
16216
16217             // word too long?  restore i & len before splitting it
16218             if ((old_i-i+clen) >= width)
16219             {
16220                 i = old_i;
16221                 len = old_len;
16222             }
16223
16224             // extra space?
16225             if (i && src[i-1] == ' ')
16226                 len--;
16227
16228             if (src[i] != ' ' && src[i] != '\n')
16229             {
16230                 i--;
16231                 if (len)
16232                     len--;
16233             }
16234
16235             // now append the newline and continuation sequence
16236             if (dest)
16237                 dest[len] = '\n';
16238             len++;
16239             if (dest)
16240                 strncpy(dest+len, cseq, cseq_len);
16241             len += cseq_len;
16242             line = cseq_len;
16243             clen = cseq_len;
16244             continue;
16245         }
16246
16247         if (dest)
16248             dest[len] = src[i];
16249         len++;
16250         if (!ansi)
16251             line++;
16252         if (src[i] == '\n')
16253             line = 0;
16254         if (src[i] == 'm')
16255             ansi = 0;
16256     }
16257     if (dest && appData.debugMode)
16258     {
16259         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16260             count, width, line, len, *lp);
16261         show_bytes(debugFP, src, count);
16262         fprintf(debugFP, "\ndest: ");
16263         show_bytes(debugFP, dest, len);
16264         fprintf(debugFP, "\n");
16265     }
16266     *lp = dest ? line : old_line;
16267
16268     return len;
16269 }
16270
16271 // [HGM] vari: routines for shelving variations
16272
16273 void
16274 PushInner(int firstMove, int lastMove)
16275 {
16276         int i, j, nrMoves = lastMove - firstMove;
16277
16278         // push current tail of game on stack
16279         savedResult[storedGames] = gameInfo.result;
16280         savedDetails[storedGames] = gameInfo.resultDetails;
16281         gameInfo.resultDetails = NULL;
16282         savedFirst[storedGames] = firstMove;
16283         savedLast [storedGames] = lastMove;
16284         savedFramePtr[storedGames] = framePtr;
16285         framePtr -= nrMoves; // reserve space for the boards
16286         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16287             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16288             for(j=0; j<MOVE_LEN; j++)
16289                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16290             for(j=0; j<2*MOVE_LEN; j++)
16291                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16292             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16293             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16294             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16295             pvInfoList[firstMove+i-1].depth = 0;
16296             commentList[framePtr+i] = commentList[firstMove+i];
16297             commentList[firstMove+i] = NULL;
16298         }
16299
16300         storedGames++;
16301         forwardMostMove = firstMove; // truncate game so we can start variation
16302 }
16303
16304 void
16305 PushTail(int firstMove, int lastMove)
16306 {
16307         if(appData.icsActive) { // only in local mode
16308                 forwardMostMove = currentMove; // mimic old ICS behavior
16309                 return;
16310         }
16311         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16312
16313         PushInner(firstMove, lastMove);
16314         if(storedGames == 1) GreyRevert(FALSE);
16315 }
16316
16317 void
16318 PopInner(Boolean annotate)
16319 {
16320         int i, j, nrMoves;
16321         char buf[8000], moveBuf[20];
16322
16323         storedGames--;
16324         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16325         nrMoves = savedLast[storedGames] - currentMove;
16326         if(annotate) {
16327                 int cnt = 10;
16328                 if(!WhiteOnMove(currentMove))
16329                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16330                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16331                 for(i=currentMove; i<forwardMostMove; i++) {
16332                         if(WhiteOnMove(i))
16333                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16334                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16335                         strcat(buf, moveBuf);
16336                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16337                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16338                 }
16339                 strcat(buf, ")");
16340         }
16341         for(i=1; i<=nrMoves; i++) { // copy last variation back
16342             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16343             for(j=0; j<MOVE_LEN; j++)
16344                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16345             for(j=0; j<2*MOVE_LEN; j++)
16346                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16347             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16348             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16349             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16350             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16351             commentList[currentMove+i] = commentList[framePtr+i];
16352             commentList[framePtr+i] = NULL;
16353         }
16354         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16355         framePtr = savedFramePtr[storedGames];
16356         gameInfo.result = savedResult[storedGames];
16357         if(gameInfo.resultDetails != NULL) {
16358             free(gameInfo.resultDetails);
16359       }
16360         gameInfo.resultDetails = savedDetails[storedGames];
16361         forwardMostMove = currentMove + nrMoves;
16362 }
16363
16364 Boolean
16365 PopTail(Boolean annotate)
16366 {
16367         if(appData.icsActive) return FALSE; // only in local mode
16368         if(!storedGames) return FALSE; // sanity
16369         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16370
16371         PopInner(annotate);
16372
16373         if(storedGames == 0) GreyRevert(TRUE);
16374         return TRUE;
16375 }
16376
16377 void
16378 CleanupTail()
16379 {       // remove all shelved variations
16380         int i;
16381         for(i=0; i<storedGames; i++) {
16382             if(savedDetails[i])
16383                 free(savedDetails[i]);
16384             savedDetails[i] = NULL;
16385         }
16386         for(i=framePtr; i<MAX_MOVES; i++) {
16387                 if(commentList[i]) free(commentList[i]);
16388                 commentList[i] = NULL;
16389         }
16390         framePtr = MAX_MOVES-1;
16391         storedGames = 0;
16392 }
16393
16394 void
16395 LoadVariation(int index, char *text)
16396 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16397         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16398         int level = 0, move;
16399
16400         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16401         // first find outermost bracketing variation
16402         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16403             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16404                 if(*p == '{') wait = '}'; else
16405                 if(*p == '[') wait = ']'; else
16406                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16407                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16408             }
16409             if(*p == wait) wait = NULLCHAR; // closing ]} found
16410             p++;
16411         }
16412         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16413         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16414         end[1] = NULLCHAR; // clip off comment beyond variation
16415         ToNrEvent(currentMove-1);
16416         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16417         // kludge: use ParsePV() to append variation to game
16418         move = currentMove;
16419         ParsePV(start, TRUE, TRUE);
16420         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16421         ClearPremoveHighlights();
16422         CommentPopDown();
16423         ToNrEvent(currentMove+1);
16424 }
16425