Let mentioning completed tourney file add one cycle
[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;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 int shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating(str)
650   char *str;
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine(ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions(ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740 "first",
741 "second"
742 };
743
744 void
745 InitEngine(ChessProgramState *cps, int n)
746 {   // [HGM] all engine initialiation put in a function that does one engine
747
748     ClearOptions(cps);
749
750     cps->which = engineNames[n];
751     cps->maybeThinking = FALSE;
752     cps->pr = NoProc;
753     cps->isr = NULL;
754     cps->sendTime = 2;
755     cps->sendDrawOffers = 1;
756
757     cps->program = appData.chessProgram[n];
758     cps->host = appData.host[n];
759     cps->dir = appData.directory[n];
760     cps->initString = appData.engInitString[n];
761     cps->computerString = appData.computerString[n];
762     cps->useSigint  = TRUE;
763     cps->useSigterm = TRUE;
764     cps->reuse = appData.reuse[n];
765     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
766     cps->useSetboard = FALSE;
767     cps->useSAN = FALSE;
768     cps->usePing = FALSE;
769     cps->lastPing = 0;
770     cps->lastPong = 0;
771     cps->usePlayother = FALSE;
772     cps->useColors = TRUE;
773     cps->useUsermove = FALSE;
774     cps->sendICS = FALSE;
775     cps->sendName = appData.icsActive;
776     cps->sdKludge = FALSE;
777     cps->stKludge = FALSE;
778     TidyProgramName(cps->program, cps->host, cps->tidy);
779     cps->matchWins = 0;
780     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781     cps->analysisSupport = 2; /* detect */
782     cps->analyzing = FALSE;
783     cps->initDone = FALSE;
784
785     /* New features added by Tord: */
786     cps->useFEN960 = FALSE;
787     cps->useOOCastle = TRUE;
788     /* End of new features added by Tord. */
789     cps->fenOverride  = appData.fenOverride[n];
790
791     /* [HGM] time odds: set factor for each machine */
792     cps->timeOdds  = appData.timeOdds[n];
793
794     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795     cps->accumulateTC = appData.accumulateTC[n];
796     cps->maxNrOfSessions = 1;
797
798     /* [HGM] debug */
799     cps->debug = FALSE;
800
801     cps->supportsNPS = UNKNOWN;
802     cps->memSize = FALSE;
803     cps->maxCores = FALSE;
804     cps->egtFormats[0] = NULLCHAR;
805
806     /* [HGM] options */
807     cps->optionSettings  = appData.engOptions[n];
808
809     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810     cps->isUCI = appData.isUCI[n]; /* [AS] */
811     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
812
813     if (appData.protocolVersion[n] > PROTOVER
814         || appData.protocolVersion[n] < 1)
815       {
816         char buf[MSG_SIZ];
817         int len;
818
819         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820                        appData.protocolVersion[n]);
821         if( (len > MSG_SIZ) && appData.debugMode )
822           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
823
824         DisplayFatalError(buf, 0, 2);
825       }
826     else
827       {
828         cps->protocolVersion = appData.protocolVersion[n];
829       }
830
831     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
832 }
833
834 ChessProgramState *savCps;
835
836 void
837 LoadEngine()
838 {
839     int i;
840     if(WaitForEngine(savCps, LoadEngine)) return;
841     CommonEngineInit(); // recalculate time odds
842     if(gameInfo.variant != StringToVariant(appData.variant)) {
843         // we changed variant when loading the engine; this forces us to reset
844         Reset(TRUE, savCps != &first);
845         EditGameEvent(); // for consistency with other path, as Reset changes mode
846     }
847     InitChessProgram(savCps, FALSE);
848     SendToProgram("force\n", savCps);
849     DisplayMessage("", "");
850     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852     ThawUI();
853     SetGNUMode();
854 }
855
856 void
857 ReplaceEngine(ChessProgramState *cps, int n)
858 {
859     EditGameEvent();
860     UnloadEngine(cps);
861     appData.noChessProgram = FALSE;
862     appData.clockMode = TRUE;
863     InitEngine(cps, n);
864     if(n) return; // only startup first engine immediately; second can wait
865     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
866     LoadEngine();
867 }
868
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871
872 static char resetOptions[] = 
873         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
880     if(engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         snprintf(command, MSG_SIZ, "%s %s", p, params);
901         p = command;
902     }
903     appData.chessProgram[i] = strdup(p);
904     appData.isUCI[i] = isUCI;
905     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906     appData.hasOwnBookUCI[i] = hasBook;
907     if(!nickName[0]) useNick = FALSE;
908     if(useNick) ASSIGN(appData.pgnName[i], nickName);
909     if(addToList) {
910         int len;
911         q = firstChessProgramNames;
912         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
913         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
914                         useNick ? " -fn \"" : "",
915                         useNick ? nickName : "",
916                         useNick ? "\"" : "",
917                         v1 ? " -firstProtocolVersion 1" : "",
918                         hasBook ? "" : " -fNoOwnBookUCI",
919                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
920                         storeVariant ? " -variant " : "",
921                         storeVariant ? VariantName(gameInfo.variant) : "");
922         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
923         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
924         if(q)   free(q);
925     }
926     ReplaceEngine(cps, i);
927 }
928
929 void
930 InitTimeControls()
931 {
932     int matched, min, sec;
933     /*
934      * Parse timeControl resource
935      */
936     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
937                           appData.movesPerSession)) {
938         char buf[MSG_SIZ];
939         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
940         DisplayFatalError(buf, 0, 2);
941     }
942
943     /*
944      * Parse searchTime resource
945      */
946     if (*appData.searchTime != NULLCHAR) {
947         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
948         if (matched == 1) {
949             searchTime = min * 60;
950         } else if (matched == 2) {
951             searchTime = min * 60 + sec;
952         } else {
953             char buf[MSG_SIZ];
954             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
955             DisplayFatalError(buf, 0, 2);
956         }
957     }
958 }
959
960 void
961 InitBackEnd1()
962 {
963
964     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
965     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
966
967     GetTimeMark(&programStartTime);
968     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
969     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
970
971     ClearProgramStats();
972     programStats.ok_to_send = 1;
973     programStats.seen_stat = 0;
974
975     /*
976      * Initialize game list
977      */
978     ListNew(&gameList);
979
980
981     /*
982      * Internet chess server status
983      */
984     if (appData.icsActive) {
985         appData.matchMode = FALSE;
986         appData.matchGames = 0;
987 #if ZIPPY
988         appData.noChessProgram = !appData.zippyPlay;
989 #else
990         appData.zippyPlay = FALSE;
991         appData.zippyTalk = FALSE;
992         appData.noChessProgram = TRUE;
993 #endif
994         if (*appData.icsHelper != NULLCHAR) {
995             appData.useTelnet = TRUE;
996             appData.telnetProgram = appData.icsHelper;
997         }
998     } else {
999         appData.zippyTalk = appData.zippyPlay = FALSE;
1000     }
1001
1002     /* [AS] Initialize pv info list [HGM] and game state */
1003     {
1004         int i, j;
1005
1006         for( i=0; i<=framePtr; i++ ) {
1007             pvInfoList[i].depth = -1;
1008             boards[i][EP_STATUS] = EP_NONE;
1009             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1010         }
1011     }
1012
1013     InitTimeControls();
1014
1015     /* [AS] Adjudication threshold */
1016     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1017
1018     InitEngine(&first, 0);
1019     InitEngine(&second, 1);
1020     CommonEngineInit();
1021
1022     if (appData.icsActive) {
1023         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1024     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1025         appData.clockMode = FALSE;
1026         first.sendTime = second.sendTime = 0;
1027     }
1028
1029 #if ZIPPY
1030     /* Override some settings from environment variables, for backward
1031        compatibility.  Unfortunately it's not feasible to have the env
1032        vars just set defaults, at least in xboard.  Ugh.
1033     */
1034     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1035       ZippyInit();
1036     }
1037 #endif
1038
1039     if (!appData.icsActive) {
1040       char buf[MSG_SIZ];
1041       int len;
1042
1043       /* Check for variants that are supported only in ICS mode,
1044          or not at all.  Some that are accepted here nevertheless
1045          have bugs; see comments below.
1046       */
1047       VariantClass variant = StringToVariant(appData.variant);
1048       switch (variant) {
1049       case VariantBughouse:     /* need four players and two boards */
1050       case VariantKriegspiel:   /* need to hide pieces and move details */
1051         /* case VariantFischeRandom: (Fabien: moved below) */
1052         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1053         if( (len > MSG_SIZ) && appData.debugMode )
1054           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1055
1056         DisplayFatalError(buf, 0, 2);
1057         return;
1058
1059       case VariantUnknown:
1060       case VariantLoadable:
1061       case Variant29:
1062       case Variant30:
1063       case Variant31:
1064       case Variant32:
1065       case Variant33:
1066       case Variant34:
1067       case Variant35:
1068       case Variant36:
1069       default:
1070         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1071         if( (len > MSG_SIZ) && appData.debugMode )
1072           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1073
1074         DisplayFatalError(buf, 0, 2);
1075         return;
1076
1077       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1078       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1079       case VariantGothic:     /* [HGM] should work */
1080       case VariantCapablanca: /* [HGM] should work */
1081       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1082       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1083       case VariantKnightmate: /* [HGM] should work */
1084       case VariantCylinder:   /* [HGM] untested */
1085       case VariantFalcon:     /* [HGM] untested */
1086       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1087                                  offboard interposition not understood */
1088       case VariantNormal:     /* definitely works! */
1089       case VariantWildCastle: /* pieces not automatically shuffled */
1090       case VariantNoCastle:   /* pieces not automatically shuffled */
1091       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1092       case VariantLosers:     /* should work except for win condition,
1093                                  and doesn't know captures are mandatory */
1094       case VariantSuicide:    /* should work except for win condition,
1095                                  and doesn't know captures are mandatory */
1096       case VariantGiveaway:   /* should work except for win condition,
1097                                  and doesn't know captures are mandatory */
1098       case VariantTwoKings:   /* should work */
1099       case VariantAtomic:     /* should work except for win condition */
1100       case Variant3Check:     /* should work except for win condition */
1101       case VariantShatranj:   /* should work except for all win conditions */
1102       case VariantMakruk:     /* should work except for daw countdown */
1103       case VariantBerolina:   /* might work if TestLegality is off */
1104       case VariantCapaRandom: /* should work */
1105       case VariantJanus:      /* should work */
1106       case VariantSuper:      /* experimental */
1107       case VariantGreat:      /* experimental, requires legality testing to be off */
1108       case VariantSChess:     /* S-Chess, should work */
1109       case VariantSpartan:    /* should work */
1110         break;
1111       }
1112     }
1113
1114 }
1115
1116 int NextIntegerFromString( char ** str, long * value )
1117 {
1118     int result = -1;
1119     char * s = *str;
1120
1121     while( *s == ' ' || *s == '\t' ) {
1122         s++;
1123     }
1124
1125     *value = 0;
1126
1127     if( *s >= '0' && *s <= '9' ) {
1128         while( *s >= '0' && *s <= '9' ) {
1129             *value = *value * 10 + (*s - '0');
1130             s++;
1131         }
1132
1133         result = 0;
1134     }
1135
1136     *str = s;
1137
1138     return result;
1139 }
1140
1141 int NextTimeControlFromString( char ** str, long * value )
1142 {
1143     long temp;
1144     int result = NextIntegerFromString( str, &temp );
1145
1146     if( result == 0 ) {
1147         *value = temp * 60; /* Minutes */
1148         if( **str == ':' ) {
1149             (*str)++;
1150             result = NextIntegerFromString( str, &temp );
1151             *value += temp; /* Seconds */
1152         }
1153     }
1154
1155     return result;
1156 }
1157
1158 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1159 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1160     int result = -1, type = 0; long temp, temp2;
1161
1162     if(**str != ':') return -1; // old params remain in force!
1163     (*str)++;
1164     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1165     if( NextIntegerFromString( str, &temp ) ) return -1;
1166     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1167
1168     if(**str != '/') {
1169         /* time only: incremental or sudden-death time control */
1170         if(**str == '+') { /* increment follows; read it */
1171             (*str)++;
1172             if(**str == '!') type = *(*str)++; // Bronstein TC
1173             if(result = NextIntegerFromString( str, &temp2)) return -1;
1174             *inc = temp2 * 1000;
1175             if(**str == '.') { // read fraction of increment
1176                 char *start = ++(*str);
1177                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1178                 temp2 *= 1000;
1179                 while(start++ < *str) temp2 /= 10;
1180                 *inc += temp2;
1181             }
1182         } else *inc = 0;
1183         *moves = 0; *tc = temp * 1000; *incType = type;
1184         return 0;
1185     }
1186
1187     (*str)++; /* classical time control */
1188     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1189
1190     if(result == 0) {
1191         *moves = temp;
1192         *tc    = temp2 * 1000;
1193         *inc   = 0;
1194         *incType = type;
1195     }
1196     return result;
1197 }
1198
1199 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1200 {   /* [HGM] get time to add from the multi-session time-control string */
1201     int incType, moves=1; /* kludge to force reading of first session */
1202     long time, increment;
1203     char *s = tcString;
1204
1205     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1206     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1207     do {
1208         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1209         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1210         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1211         if(movenr == -1) return time;    /* last move before new session     */
1212         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1213         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1214         if(!moves) return increment;     /* current session is incremental   */
1215         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1216     } while(movenr >= -1);               /* try again for next session       */
1217
1218     return 0; // no new time quota on this move
1219 }
1220
1221 int
1222 ParseTimeControl(tc, ti, mps)
1223      char *tc;
1224      float ti;
1225      int mps;
1226 {
1227   long tc1;
1228   long tc2;
1229   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1230   int min, sec=0;
1231
1232   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1233   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1234       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1235   if(ti > 0) {
1236
1237     if(mps)
1238       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1239     else 
1240       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1241   } else {
1242     if(mps)
1243       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1244     else 
1245       snprintf(buf, MSG_SIZ, ":%s", mytc);
1246   }
1247   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1248   
1249   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1250     return FALSE;
1251   }
1252
1253   if( *tc == '/' ) {
1254     /* Parse second time control */
1255     tc++;
1256
1257     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1258       return FALSE;
1259     }
1260
1261     if( tc2 == 0 ) {
1262       return FALSE;
1263     }
1264
1265     timeControl_2 = tc2 * 1000;
1266   }
1267   else {
1268     timeControl_2 = 0;
1269   }
1270
1271   if( tc1 == 0 ) {
1272     return FALSE;
1273   }
1274
1275   timeControl = tc1 * 1000;
1276
1277   if (ti >= 0) {
1278     timeIncrement = ti * 1000;  /* convert to ms */
1279     movesPerSession = 0;
1280   } else {
1281     timeIncrement = 0;
1282     movesPerSession = mps;
1283   }
1284   return TRUE;
1285 }
1286
1287 void
1288 InitBackEnd2()
1289 {
1290     if (appData.debugMode) {
1291         fprintf(debugFP, "%s\n", programVersion);
1292     }
1293
1294     set_cont_sequence(appData.wrapContSeq);
1295     if (appData.matchGames > 0) {
1296         appData.matchMode = TRUE;
1297     } else if (appData.matchMode) {
1298         appData.matchGames = 1;
1299     }
1300     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1301         appData.matchGames = appData.sameColorGames;
1302     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1303         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1304         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1305     }
1306     Reset(TRUE, FALSE);
1307     if (appData.noChessProgram || first.protocolVersion == 1) {
1308       InitBackEnd3();
1309     } else {
1310       /* kludge: allow timeout for initial "feature" commands */
1311       FreezeUI();
1312       DisplayMessage("", _("Starting chess program"));
1313       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1314     }
1315 }
1316
1317 int
1318 CalculateIndex(int index, int gameNr)
1319 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1320     int res;
1321     if(index > 0) return index; // fixed nmber
1322     if(index == 0) return 1;
1323     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1324     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1325     return res;
1326 }
1327
1328 int
1329 LoadGameOrPosition(int gameNr)
1330 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1331     if (*appData.loadGameFile != NULLCHAR) {
1332         if (!LoadGameFromFile(appData.loadGameFile,
1333                 CalculateIndex(appData.loadGameIndex, gameNr),
1334                               appData.loadGameFile, FALSE)) {
1335             DisplayFatalError(_("Bad game file"), 0, 1);
1336             return 0;
1337         }
1338     } else if (*appData.loadPositionFile != NULLCHAR) {
1339         if (!LoadPositionFromFile(appData.loadPositionFile,
1340                 CalculateIndex(appData.loadPositionIndex, gameNr),
1341                                   appData.loadPositionFile)) {
1342             DisplayFatalError(_("Bad position file"), 0, 1);
1343             return 0;
1344         }
1345     }
1346     return 1;
1347 }
1348
1349 void
1350 ReserveGame(int gameNr, char resChar)
1351 {
1352     FILE *tf = fopen(appData.tourneyFile, "r+");
1353     char *p, *q, c, buf[MSG_SIZ];
1354     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1355     safeStrCpy(buf, lastMsg, MSG_SIZ);
1356     DisplayMessage(_("Pick new game"), "");
1357     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1358     ParseArgsFromFile(tf);
1359     p = q = appData.results;
1360     if(appData.debugMode) {
1361       char *r = appData.participants;
1362       fprintf(debugFP, "results = '%s'\n", p);
1363       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1364       fprintf(debugFP, "\n");
1365     }
1366     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1367     nextGame = q - p;
1368     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1369     safeStrCpy(q, p, strlen(p) + 2);
1370     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1371     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1372     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1373         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1374         q[nextGame] = '*';
1375     }
1376     fseek(tf, -(strlen(p)+4), SEEK_END);
1377     c = fgetc(tf);
1378     if(c != '"') // depending on DOS or Unix line endings we can be one off
1379          fseek(tf, -(strlen(p)+2), SEEK_END);
1380     else fseek(tf, -(strlen(p)+3), SEEK_END);
1381     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1382     DisplayMessage(buf, "");
1383     free(p); appData.results = q;
1384     if(nextGame <= appData.matchGames && resChar != ' ' &&
1385        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1386         UnloadEngine(&first);  // next game belongs to other pairing;
1387         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1388     }
1389 }
1390
1391 void
1392 MatchEvent(int mode)
1393 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1394         int dummy;
1395         if(matchMode) { // already in match mode: switch it off
1396             abortMatch = TRUE;
1397             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1398             ModeHighlight(); // kludgey way to remove checkmark...
1399             return;
1400         }
1401 //      if(gameMode != BeginningOfGame) {
1402 //          DisplayError(_("You can only start a match from the initial position."), 0);
1403 //          return;
1404 //      }
1405         abortMatch = FALSE;
1406         appData.matchGames = appData.defaultMatchGames;
1407         /* Set up machine vs. machine match */
1408         nextGame = 0;
1409         NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1410         if(appData.tourneyFile[0]) {
1411             ReserveGame(-1, 0);
1412             if(nextGame > appData.matchGames) {
1413                 char buf[MSG_SIZ];
1414                 if(strchr(appData.results, '*') == NULL) {
1415                     FILE *f;
1416                     appData.tourneyCycles++;
1417                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1418                         fclose(f);
1419                         NextTourneyGame(-1, &dummy);
1420                         ReserveGame(-1, 0);
1421                         if(nextGame <= appData.matchGames) {
1422                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1423                             matchMode = mode;
1424                             ScheduleDelayedEvent(NextMatchGame, 10000);
1425                             return;
1426                         }
1427                     }
1428                 }
1429                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1430                 DisplayError(buf, 0);
1431                 appData.tourneyFile[0] = 0;
1432                 return;
1433             }
1434         } else
1435         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1436             DisplayFatalError(_("Can't have a match with no chess programs"),
1437                               0, 2);
1438             return;
1439         }
1440         matchMode = mode;
1441         matchGame = roundNr = 1;
1442         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1443         NextMatchGame();
1444 }
1445
1446 void
1447 InitBackEnd3 P((void))
1448 {
1449     GameMode initialMode;
1450     char buf[MSG_SIZ];
1451     int err, len;
1452
1453     InitChessProgram(&first, startedFromSetupPosition);
1454
1455     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1456         free(programVersion);
1457         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1458         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1459     }
1460
1461     if (appData.icsActive) {
1462 #ifdef WIN32
1463         /* [DM] Make a console window if needed [HGM] merged ifs */
1464         ConsoleCreate();
1465 #endif
1466         err = establish();
1467         if (err != 0)
1468           {
1469             if (*appData.icsCommPort != NULLCHAR)
1470               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1471                              appData.icsCommPort);
1472             else
1473               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1474                         appData.icsHost, appData.icsPort);
1475
1476             if( (len > MSG_SIZ) && appData.debugMode )
1477               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1478
1479             DisplayFatalError(buf, err, 1);
1480             return;
1481         }
1482         SetICSMode();
1483         telnetISR =
1484           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1485         fromUserISR =
1486           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1487         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1488             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1489     } else if (appData.noChessProgram) {
1490         SetNCPMode();
1491     } else {
1492         SetGNUMode();
1493     }
1494
1495     if (*appData.cmailGameName != NULLCHAR) {
1496         SetCmailMode();
1497         OpenLoopback(&cmailPR);
1498         cmailISR =
1499           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1500     }
1501
1502     ThawUI();
1503     DisplayMessage("", "");
1504     if (StrCaseCmp(appData.initialMode, "") == 0) {
1505       initialMode = BeginningOfGame;
1506       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1507         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1508         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1509         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1510         ModeHighlight();
1511       }
1512     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1513       initialMode = TwoMachinesPlay;
1514     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1515       initialMode = AnalyzeFile;
1516     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1517       initialMode = AnalyzeMode;
1518     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1519       initialMode = MachinePlaysWhite;
1520     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1521       initialMode = MachinePlaysBlack;
1522     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1523       initialMode = EditGame;
1524     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1525       initialMode = EditPosition;
1526     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1527       initialMode = Training;
1528     } else {
1529       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1530       if( (len > MSG_SIZ) && appData.debugMode )
1531         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532
1533       DisplayFatalError(buf, 0, 2);
1534       return;
1535     }
1536
1537     if (appData.matchMode) {
1538         if(appData.tourneyFile[0]) { // start tourney from command line
1539             FILE *f;
1540             if(f = fopen(appData.tourneyFile, "r")) {
1541                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1542                 fclose(f);
1543             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1544         }
1545         MatchEvent(TRUE);
1546     } else if (*appData.cmailGameName != NULLCHAR) {
1547         /* Set up cmail mode */
1548         ReloadCmailMsgEvent(TRUE);
1549     } else {
1550         /* Set up other modes */
1551         if (initialMode == AnalyzeFile) {
1552           if (*appData.loadGameFile == NULLCHAR) {
1553             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1554             return;
1555           }
1556         }
1557         if (*appData.loadGameFile != NULLCHAR) {
1558             (void) LoadGameFromFile(appData.loadGameFile,
1559                                     appData.loadGameIndex,
1560                                     appData.loadGameFile, TRUE);
1561         } else if (*appData.loadPositionFile != NULLCHAR) {
1562             (void) LoadPositionFromFile(appData.loadPositionFile,
1563                                         appData.loadPositionIndex,
1564                                         appData.loadPositionFile);
1565             /* [HGM] try to make self-starting even after FEN load */
1566             /* to allow automatic setup of fairy variants with wtm */
1567             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1568                 gameMode = BeginningOfGame;
1569                 setboardSpoiledMachineBlack = 1;
1570             }
1571             /* [HGM] loadPos: make that every new game uses the setup */
1572             /* from file as long as we do not switch variant          */
1573             if(!blackPlaysFirst) {
1574                 startedFromPositionFile = TRUE;
1575                 CopyBoard(filePosition, boards[0]);
1576             }
1577         }
1578         if (initialMode == AnalyzeMode) {
1579           if (appData.noChessProgram) {
1580             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1581             return;
1582           }
1583           if (appData.icsActive) {
1584             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1585             return;
1586           }
1587           AnalyzeModeEvent();
1588         } else if (initialMode == AnalyzeFile) {
1589           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1590           ShowThinkingEvent();
1591           AnalyzeFileEvent();
1592           AnalysisPeriodicEvent(1);
1593         } else if (initialMode == MachinePlaysWhite) {
1594           if (appData.noChessProgram) {
1595             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1596                               0, 2);
1597             return;
1598           }
1599           if (appData.icsActive) {
1600             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1601                               0, 2);
1602             return;
1603           }
1604           MachineWhiteEvent();
1605         } else if (initialMode == MachinePlaysBlack) {
1606           if (appData.noChessProgram) {
1607             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1608                               0, 2);
1609             return;
1610           }
1611           if (appData.icsActive) {
1612             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1613                               0, 2);
1614             return;
1615           }
1616           MachineBlackEvent();
1617         } else if (initialMode == TwoMachinesPlay) {
1618           if (appData.noChessProgram) {
1619             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1620                               0, 2);
1621             return;
1622           }
1623           if (appData.icsActive) {
1624             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1625                               0, 2);
1626             return;
1627           }
1628           TwoMachinesEvent();
1629         } else if (initialMode == EditGame) {
1630           EditGameEvent();
1631         } else if (initialMode == EditPosition) {
1632           EditPositionEvent();
1633         } else if (initialMode == Training) {
1634           if (*appData.loadGameFile == NULLCHAR) {
1635             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1636             return;
1637           }
1638           TrainingEvent();
1639         }
1640     }
1641 }
1642
1643 /*
1644  * Establish will establish a contact to a remote host.port.
1645  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1646  *  used to talk to the host.
1647  * Returns 0 if okay, error code if not.
1648  */
1649 int
1650 establish()
1651 {
1652     char buf[MSG_SIZ];
1653
1654     if (*appData.icsCommPort != NULLCHAR) {
1655         /* Talk to the host through a serial comm port */
1656         return OpenCommPort(appData.icsCommPort, &icsPR);
1657
1658     } else if (*appData.gateway != NULLCHAR) {
1659         if (*appData.remoteShell == NULLCHAR) {
1660             /* Use the rcmd protocol to run telnet program on a gateway host */
1661             snprintf(buf, sizeof(buf), "%s %s %s",
1662                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1663             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1664
1665         } else {
1666             /* Use the rsh program to run telnet program on a gateway host */
1667             if (*appData.remoteUser == NULLCHAR) {
1668                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1669                         appData.gateway, appData.telnetProgram,
1670                         appData.icsHost, appData.icsPort);
1671             } else {
1672                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1673                         appData.remoteShell, appData.gateway,
1674                         appData.remoteUser, appData.telnetProgram,
1675                         appData.icsHost, appData.icsPort);
1676             }
1677             return StartChildProcess(buf, "", &icsPR);
1678
1679         }
1680     } else if (appData.useTelnet) {
1681         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1682
1683     } else {
1684         /* TCP socket interface differs somewhat between
1685            Unix and NT; handle details in the front end.
1686            */
1687         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1688     }
1689 }
1690
1691 void EscapeExpand(char *p, char *q)
1692 {       // [HGM] initstring: routine to shape up string arguments
1693         while(*p++ = *q++) if(p[-1] == '\\')
1694             switch(*q++) {
1695                 case 'n': p[-1] = '\n'; break;
1696                 case 'r': p[-1] = '\r'; break;
1697                 case 't': p[-1] = '\t'; break;
1698                 case '\\': p[-1] = '\\'; break;
1699                 case 0: *p = 0; return;
1700                 default: p[-1] = q[-1]; break;
1701             }
1702 }
1703
1704 void
1705 show_bytes(fp, buf, count)
1706      FILE *fp;
1707      char *buf;
1708      int count;
1709 {
1710     while (count--) {
1711         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1712             fprintf(fp, "\\%03o", *buf & 0xff);
1713         } else {
1714             putc(*buf, fp);
1715         }
1716         buf++;
1717     }
1718     fflush(fp);
1719 }
1720
1721 /* Returns an errno value */
1722 int
1723 OutputMaybeTelnet(pr, message, count, outError)
1724      ProcRef pr;
1725      char *message;
1726      int count;
1727      int *outError;
1728 {
1729     char buf[8192], *p, *q, *buflim;
1730     int left, newcount, outcount;
1731
1732     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1733         *appData.gateway != NULLCHAR) {
1734         if (appData.debugMode) {
1735             fprintf(debugFP, ">ICS: ");
1736             show_bytes(debugFP, message, count);
1737             fprintf(debugFP, "\n");
1738         }
1739         return OutputToProcess(pr, message, count, outError);
1740     }
1741
1742     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1743     p = message;
1744     q = buf;
1745     left = count;
1746     newcount = 0;
1747     while (left) {
1748         if (q >= buflim) {
1749             if (appData.debugMode) {
1750                 fprintf(debugFP, ">ICS: ");
1751                 show_bytes(debugFP, buf, newcount);
1752                 fprintf(debugFP, "\n");
1753             }
1754             outcount = OutputToProcess(pr, buf, newcount, outError);
1755             if (outcount < newcount) return -1; /* to be sure */
1756             q = buf;
1757             newcount = 0;
1758         }
1759         if (*p == '\n') {
1760             *q++ = '\r';
1761             newcount++;
1762         } else if (((unsigned char) *p) == TN_IAC) {
1763             *q++ = (char) TN_IAC;
1764             newcount ++;
1765         }
1766         *q++ = *p++;
1767         newcount++;
1768         left--;
1769     }
1770     if (appData.debugMode) {
1771         fprintf(debugFP, ">ICS: ");
1772         show_bytes(debugFP, buf, newcount);
1773         fprintf(debugFP, "\n");
1774     }
1775     outcount = OutputToProcess(pr, buf, newcount, outError);
1776     if (outcount < newcount) return -1; /* to be sure */
1777     return count;
1778 }
1779
1780 void
1781 read_from_player(isr, closure, message, count, error)
1782      InputSourceRef isr;
1783      VOIDSTAR closure;
1784      char *message;
1785      int count;
1786      int error;
1787 {
1788     int outError, outCount;
1789     static int gotEof = 0;
1790
1791     /* Pass data read from player on to ICS */
1792     if (count > 0) {
1793         gotEof = 0;
1794         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1795         if (outCount < count) {
1796             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1797         }
1798     } else if (count < 0) {
1799         RemoveInputSource(isr);
1800         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1801     } else if (gotEof++ > 0) {
1802         RemoveInputSource(isr);
1803         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1804     }
1805 }
1806
1807 void
1808 KeepAlive()
1809 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1810     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1811     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1812     SendToICS("date\n");
1813     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1814 }
1815
1816 /* added routine for printf style output to ics */
1817 void ics_printf(char *format, ...)
1818 {
1819     char buffer[MSG_SIZ];
1820     va_list args;
1821
1822     va_start(args, format);
1823     vsnprintf(buffer, sizeof(buffer), format, args);
1824     buffer[sizeof(buffer)-1] = '\0';
1825     SendToICS(buffer);
1826     va_end(args);
1827 }
1828
1829 void
1830 SendToICS(s)
1831      char *s;
1832 {
1833     int count, outCount, outError;
1834
1835     if (icsPR == NULL) return;
1836
1837     count = strlen(s);
1838     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1839     if (outCount < count) {
1840         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1841     }
1842 }
1843
1844 /* This is used for sending logon scripts to the ICS. Sending
1845    without a delay causes problems when using timestamp on ICC
1846    (at least on my machine). */
1847 void
1848 SendToICSDelayed(s,msdelay)
1849      char *s;
1850      long msdelay;
1851 {
1852     int count, outCount, outError;
1853
1854     if (icsPR == NULL) return;
1855
1856     count = strlen(s);
1857     if (appData.debugMode) {
1858         fprintf(debugFP, ">ICS: ");
1859         show_bytes(debugFP, s, count);
1860         fprintf(debugFP, "\n");
1861     }
1862     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1863                                       msdelay);
1864     if (outCount < count) {
1865         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1866     }
1867 }
1868
1869
1870 /* Remove all highlighting escape sequences in s
1871    Also deletes any suffix starting with '('
1872    */
1873 char *
1874 StripHighlightAndTitle(s)
1875      char *s;
1876 {
1877     static char retbuf[MSG_SIZ];
1878     char *p = retbuf;
1879
1880     while (*s != NULLCHAR) {
1881         while (*s == '\033') {
1882             while (*s != NULLCHAR && !isalpha(*s)) s++;
1883             if (*s != NULLCHAR) s++;
1884         }
1885         while (*s != NULLCHAR && *s != '\033') {
1886             if (*s == '(' || *s == '[') {
1887                 *p = NULLCHAR;
1888                 return retbuf;
1889             }
1890             *p++ = *s++;
1891         }
1892     }
1893     *p = NULLCHAR;
1894     return retbuf;
1895 }
1896
1897 /* Remove all highlighting escape sequences in s */
1898 char *
1899 StripHighlight(s)
1900      char *s;
1901 {
1902     static char retbuf[MSG_SIZ];
1903     char *p = retbuf;
1904
1905     while (*s != NULLCHAR) {
1906         while (*s == '\033') {
1907             while (*s != NULLCHAR && !isalpha(*s)) s++;
1908             if (*s != NULLCHAR) s++;
1909         }
1910         while (*s != NULLCHAR && *s != '\033') {
1911             *p++ = *s++;
1912         }
1913     }
1914     *p = NULLCHAR;
1915     return retbuf;
1916 }
1917
1918 char *variantNames[] = VARIANT_NAMES;
1919 char *
1920 VariantName(v)
1921      VariantClass v;
1922 {
1923     return variantNames[v];
1924 }
1925
1926
1927 /* Identify a variant from the strings the chess servers use or the
1928    PGN Variant tag names we use. */
1929 VariantClass
1930 StringToVariant(e)
1931      char *e;
1932 {
1933     char *p;
1934     int wnum = -1;
1935     VariantClass v = VariantNormal;
1936     int i, found = FALSE;
1937     char buf[MSG_SIZ];
1938     int len;
1939
1940     if (!e) return v;
1941
1942     /* [HGM] skip over optional board-size prefixes */
1943     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1944         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1945         while( *e++ != '_');
1946     }
1947
1948     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1949         v = VariantNormal;
1950         found = TRUE;
1951     } else
1952     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1953       if (StrCaseStr(e, variantNames[i])) {
1954         v = (VariantClass) i;
1955         found = TRUE;
1956         break;
1957       }
1958     }
1959
1960     if (!found) {
1961       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1962           || StrCaseStr(e, "wild/fr")
1963           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1964         v = VariantFischeRandom;
1965       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1966                  (i = 1, p = StrCaseStr(e, "w"))) {
1967         p += i;
1968         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1969         if (isdigit(*p)) {
1970           wnum = atoi(p);
1971         } else {
1972           wnum = -1;
1973         }
1974         switch (wnum) {
1975         case 0: /* FICS only, actually */
1976         case 1:
1977           /* Castling legal even if K starts on d-file */
1978           v = VariantWildCastle;
1979           break;
1980         case 2:
1981         case 3:
1982         case 4:
1983           /* Castling illegal even if K & R happen to start in
1984              normal positions. */
1985           v = VariantNoCastle;
1986           break;
1987         case 5:
1988         case 7:
1989         case 8:
1990         case 10:
1991         case 11:
1992         case 12:
1993         case 13:
1994         case 14:
1995         case 15:
1996         case 18:
1997         case 19:
1998           /* Castling legal iff K & R start in normal positions */
1999           v = VariantNormal;
2000           break;
2001         case 6:
2002         case 20:
2003         case 21:
2004           /* Special wilds for position setup; unclear what to do here */
2005           v = VariantLoadable;
2006           break;
2007         case 9:
2008           /* Bizarre ICC game */
2009           v = VariantTwoKings;
2010           break;
2011         case 16:
2012           v = VariantKriegspiel;
2013           break;
2014         case 17:
2015           v = VariantLosers;
2016           break;
2017         case 22:
2018           v = VariantFischeRandom;
2019           break;
2020         case 23:
2021           v = VariantCrazyhouse;
2022           break;
2023         case 24:
2024           v = VariantBughouse;
2025           break;
2026         case 25:
2027           v = Variant3Check;
2028           break;
2029         case 26:
2030           /* Not quite the same as FICS suicide! */
2031           v = VariantGiveaway;
2032           break;
2033         case 27:
2034           v = VariantAtomic;
2035           break;
2036         case 28:
2037           v = VariantShatranj;
2038           break;
2039
2040         /* Temporary names for future ICC types.  The name *will* change in
2041            the next xboard/WinBoard release after ICC defines it. */
2042         case 29:
2043           v = Variant29;
2044           break;
2045         case 30:
2046           v = Variant30;
2047           break;
2048         case 31:
2049           v = Variant31;
2050           break;
2051         case 32:
2052           v = Variant32;
2053           break;
2054         case 33:
2055           v = Variant33;
2056           break;
2057         case 34:
2058           v = Variant34;
2059           break;
2060         case 35:
2061           v = Variant35;
2062           break;
2063         case 36:
2064           v = Variant36;
2065           break;
2066         case 37:
2067           v = VariantShogi;
2068           break;
2069         case 38:
2070           v = VariantXiangqi;
2071           break;
2072         case 39:
2073           v = VariantCourier;
2074           break;
2075         case 40:
2076           v = VariantGothic;
2077           break;
2078         case 41:
2079           v = VariantCapablanca;
2080           break;
2081         case 42:
2082           v = VariantKnightmate;
2083           break;
2084         case 43:
2085           v = VariantFairy;
2086           break;
2087         case 44:
2088           v = VariantCylinder;
2089           break;
2090         case 45:
2091           v = VariantFalcon;
2092           break;
2093         case 46:
2094           v = VariantCapaRandom;
2095           break;
2096         case 47:
2097           v = VariantBerolina;
2098           break;
2099         case 48:
2100           v = VariantJanus;
2101           break;
2102         case 49:
2103           v = VariantSuper;
2104           break;
2105         case 50:
2106           v = VariantGreat;
2107           break;
2108         case -1:
2109           /* Found "wild" or "w" in the string but no number;
2110              must assume it's normal chess. */
2111           v = VariantNormal;
2112           break;
2113         default:
2114           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2115           if( (len > MSG_SIZ) && appData.debugMode )
2116             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2117
2118           DisplayError(buf, 0);
2119           v = VariantUnknown;
2120           break;
2121         }
2122       }
2123     }
2124     if (appData.debugMode) {
2125       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2126               e, wnum, VariantName(v));
2127     }
2128     return v;
2129 }
2130
2131 static int leftover_start = 0, leftover_len = 0;
2132 char star_match[STAR_MATCH_N][MSG_SIZ];
2133
2134 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2135    advance *index beyond it, and set leftover_start to the new value of
2136    *index; else return FALSE.  If pattern contains the character '*', it
2137    matches any sequence of characters not containing '\r', '\n', or the
2138    character following the '*' (if any), and the matched sequence(s) are
2139    copied into star_match.
2140    */
2141 int
2142 looking_at(buf, index, pattern)
2143      char *buf;
2144      int *index;
2145      char *pattern;
2146 {
2147     char *bufp = &buf[*index], *patternp = pattern;
2148     int star_count = 0;
2149     char *matchp = star_match[0];
2150
2151     for (;;) {
2152         if (*patternp == NULLCHAR) {
2153             *index = leftover_start = bufp - buf;
2154             *matchp = NULLCHAR;
2155             return TRUE;
2156         }
2157         if (*bufp == NULLCHAR) return FALSE;
2158         if (*patternp == '*') {
2159             if (*bufp == *(patternp + 1)) {
2160                 *matchp = NULLCHAR;
2161                 matchp = star_match[++star_count];
2162                 patternp += 2;
2163                 bufp++;
2164                 continue;
2165             } else if (*bufp == '\n' || *bufp == '\r') {
2166                 patternp++;
2167                 if (*patternp == NULLCHAR)
2168                   continue;
2169                 else
2170                   return FALSE;
2171             } else {
2172                 *matchp++ = *bufp++;
2173                 continue;
2174             }
2175         }
2176         if (*patternp != *bufp) return FALSE;
2177         patternp++;
2178         bufp++;
2179     }
2180 }
2181
2182 void
2183 SendToPlayer(data, length)
2184      char *data;
2185      int length;
2186 {
2187     int error, outCount;
2188     outCount = OutputToProcess(NoProc, data, length, &error);
2189     if (outCount < length) {
2190         DisplayFatalError(_("Error writing to display"), error, 1);
2191     }
2192 }
2193
2194 void
2195 PackHolding(packed, holding)
2196      char packed[];
2197      char *holding;
2198 {
2199     char *p = holding;
2200     char *q = packed;
2201     int runlength = 0;
2202     int curr = 9999;
2203     do {
2204         if (*p == curr) {
2205             runlength++;
2206         } else {
2207             switch (runlength) {
2208               case 0:
2209                 break;
2210               case 1:
2211                 *q++ = curr;
2212                 break;
2213               case 2:
2214                 *q++ = curr;
2215                 *q++ = curr;
2216                 break;
2217               default:
2218                 sprintf(q, "%d", runlength);
2219                 while (*q) q++;
2220                 *q++ = curr;
2221                 break;
2222             }
2223             runlength = 1;
2224             curr = *p;
2225         }
2226     } while (*p++);
2227     *q = NULLCHAR;
2228 }
2229
2230 /* Telnet protocol requests from the front end */
2231 void
2232 TelnetRequest(ddww, option)
2233      unsigned char ddww, option;
2234 {
2235     unsigned char msg[3];
2236     int outCount, outError;
2237
2238     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2239
2240     if (appData.debugMode) {
2241         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2242         switch (ddww) {
2243           case TN_DO:
2244             ddwwStr = "DO";
2245             break;
2246           case TN_DONT:
2247             ddwwStr = "DONT";
2248             break;
2249           case TN_WILL:
2250             ddwwStr = "WILL";
2251             break;
2252           case TN_WONT:
2253             ddwwStr = "WONT";
2254             break;
2255           default:
2256             ddwwStr = buf1;
2257             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2258             break;
2259         }
2260         switch (option) {
2261           case TN_ECHO:
2262             optionStr = "ECHO";
2263             break;
2264           default:
2265             optionStr = buf2;
2266             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2267             break;
2268         }
2269         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2270     }
2271     msg[0] = TN_IAC;
2272     msg[1] = ddww;
2273     msg[2] = option;
2274     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2275     if (outCount < 3) {
2276         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2277     }
2278 }
2279
2280 void
2281 DoEcho()
2282 {
2283     if (!appData.icsActive) return;
2284     TelnetRequest(TN_DO, TN_ECHO);
2285 }
2286
2287 void
2288 DontEcho()
2289 {
2290     if (!appData.icsActive) return;
2291     TelnetRequest(TN_DONT, TN_ECHO);
2292 }
2293
2294 void
2295 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2296 {
2297     /* put the holdings sent to us by the server on the board holdings area */
2298     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2299     char p;
2300     ChessSquare piece;
2301
2302     if(gameInfo.holdingsWidth < 2)  return;
2303     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2304         return; // prevent overwriting by pre-board holdings
2305
2306     if( (int)lowestPiece >= BlackPawn ) {
2307         holdingsColumn = 0;
2308         countsColumn = 1;
2309         holdingsStartRow = BOARD_HEIGHT-1;
2310         direction = -1;
2311     } else {
2312         holdingsColumn = BOARD_WIDTH-1;
2313         countsColumn = BOARD_WIDTH-2;
2314         holdingsStartRow = 0;
2315         direction = 1;
2316     }
2317
2318     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2319         board[i][holdingsColumn] = EmptySquare;
2320         board[i][countsColumn]   = (ChessSquare) 0;
2321     }
2322     while( (p=*holdings++) != NULLCHAR ) {
2323         piece = CharToPiece( ToUpper(p) );
2324         if(piece == EmptySquare) continue;
2325         /*j = (int) piece - (int) WhitePawn;*/
2326         j = PieceToNumber(piece);
2327         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2328         if(j < 0) continue;               /* should not happen */
2329         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2330         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2331         board[holdingsStartRow+j*direction][countsColumn]++;
2332     }
2333 }
2334
2335
2336 void
2337 VariantSwitch(Board board, VariantClass newVariant)
2338 {
2339    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2340    static Board oldBoard;
2341
2342    startedFromPositionFile = FALSE;
2343    if(gameInfo.variant == newVariant) return;
2344
2345    /* [HGM] This routine is called each time an assignment is made to
2346     * gameInfo.variant during a game, to make sure the board sizes
2347     * are set to match the new variant. If that means adding or deleting
2348     * holdings, we shift the playing board accordingly
2349     * This kludge is needed because in ICS observe mode, we get boards
2350     * of an ongoing game without knowing the variant, and learn about the
2351     * latter only later. This can be because of the move list we requested,
2352     * in which case the game history is refilled from the beginning anyway,
2353     * but also when receiving holdings of a crazyhouse game. In the latter
2354     * case we want to add those holdings to the already received position.
2355     */
2356
2357
2358    if (appData.debugMode) {
2359      fprintf(debugFP, "Switch board from %s to %s\n",
2360              VariantName(gameInfo.variant), VariantName(newVariant));
2361      setbuf(debugFP, NULL);
2362    }
2363    shuffleOpenings = 0;       /* [HGM] shuffle */
2364    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2365    switch(newVariant)
2366      {
2367      case VariantShogi:
2368        newWidth = 9;  newHeight = 9;
2369        gameInfo.holdingsSize = 7;
2370      case VariantBughouse:
2371      case VariantCrazyhouse:
2372        newHoldingsWidth = 2; break;
2373      case VariantGreat:
2374        newWidth = 10;
2375      case VariantSuper:
2376        newHoldingsWidth = 2;
2377        gameInfo.holdingsSize = 8;
2378        break;
2379      case VariantGothic:
2380      case VariantCapablanca:
2381      case VariantCapaRandom:
2382        newWidth = 10;
2383      default:
2384        newHoldingsWidth = gameInfo.holdingsSize = 0;
2385      };
2386
2387    if(newWidth  != gameInfo.boardWidth  ||
2388       newHeight != gameInfo.boardHeight ||
2389       newHoldingsWidth != gameInfo.holdingsWidth ) {
2390
2391      /* shift position to new playing area, if needed */
2392      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2393        for(i=0; i<BOARD_HEIGHT; i++)
2394          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2395            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2396              board[i][j];
2397        for(i=0; i<newHeight; i++) {
2398          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2399          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2400        }
2401      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2402        for(i=0; i<BOARD_HEIGHT; i++)
2403          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2404            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2405              board[i][j];
2406      }
2407      gameInfo.boardWidth  = newWidth;
2408      gameInfo.boardHeight = newHeight;
2409      gameInfo.holdingsWidth = newHoldingsWidth;
2410      gameInfo.variant = newVariant;
2411      InitDrawingSizes(-2, 0);
2412    } else gameInfo.variant = newVariant;
2413    CopyBoard(oldBoard, board);   // remember correctly formatted board
2414      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2415    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2416 }
2417
2418 static int loggedOn = FALSE;
2419
2420 /*-- Game start info cache: --*/
2421 int gs_gamenum;
2422 char gs_kind[MSG_SIZ];
2423 static char player1Name[128] = "";
2424 static char player2Name[128] = "";
2425 static char cont_seq[] = "\n\\   ";
2426 static int player1Rating = -1;
2427 static int player2Rating = -1;
2428 /*----------------------------*/
2429
2430 ColorClass curColor = ColorNormal;
2431 int suppressKibitz = 0;
2432
2433 // [HGM] seekgraph
2434 Boolean soughtPending = FALSE;
2435 Boolean seekGraphUp;
2436 #define MAX_SEEK_ADS 200
2437 #define SQUARE 0x80
2438 char *seekAdList[MAX_SEEK_ADS];
2439 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2440 float tcList[MAX_SEEK_ADS];
2441 char colorList[MAX_SEEK_ADS];
2442 int nrOfSeekAds = 0;
2443 int minRating = 1010, maxRating = 2800;
2444 int hMargin = 10, vMargin = 20, h, w;
2445 extern int squareSize, lineGap;
2446
2447 void
2448 PlotSeekAd(int i)
2449 {
2450         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2451         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2452         if(r < minRating+100 && r >=0 ) r = minRating+100;
2453         if(r > maxRating) r = maxRating;
2454         if(tc < 1.) tc = 1.;
2455         if(tc > 95.) tc = 95.;
2456         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2457         y = ((double)r - minRating)/(maxRating - minRating)
2458             * (h-vMargin-squareSize/8-1) + vMargin;
2459         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2460         if(strstr(seekAdList[i], " u ")) color = 1;
2461         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2462            !strstr(seekAdList[i], "bullet") &&
2463            !strstr(seekAdList[i], "blitz") &&
2464            !strstr(seekAdList[i], "standard") ) color = 2;
2465         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2466         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2467 }
2468
2469 void
2470 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2471 {
2472         char buf[MSG_SIZ], *ext = "";
2473         VariantClass v = StringToVariant(type);
2474         if(strstr(type, "wild")) {
2475             ext = type + 4; // append wild number
2476             if(v == VariantFischeRandom) type = "chess960"; else
2477             if(v == VariantLoadable) type = "setup"; else
2478             type = VariantName(v);
2479         }
2480         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2481         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2482             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2483             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2484             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2485             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2486             seekNrList[nrOfSeekAds] = nr;
2487             zList[nrOfSeekAds] = 0;
2488             seekAdList[nrOfSeekAds++] = StrSave(buf);
2489             if(plot) PlotSeekAd(nrOfSeekAds-1);
2490         }
2491 }
2492
2493 void
2494 EraseSeekDot(int i)
2495 {
2496     int x = xList[i], y = yList[i], d=squareSize/4, k;
2497     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2498     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2499     // now replot every dot that overlapped
2500     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2501         int xx = xList[k], yy = yList[k];
2502         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2503             DrawSeekDot(xx, yy, colorList[k]);
2504     }
2505 }
2506
2507 void
2508 RemoveSeekAd(int nr)
2509 {
2510         int i;
2511         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2512             EraseSeekDot(i);
2513             if(seekAdList[i]) free(seekAdList[i]);
2514             seekAdList[i] = seekAdList[--nrOfSeekAds];
2515             seekNrList[i] = seekNrList[nrOfSeekAds];
2516             ratingList[i] = ratingList[nrOfSeekAds];
2517             colorList[i]  = colorList[nrOfSeekAds];
2518             tcList[i] = tcList[nrOfSeekAds];
2519             xList[i]  = xList[nrOfSeekAds];
2520             yList[i]  = yList[nrOfSeekAds];
2521             zList[i]  = zList[nrOfSeekAds];
2522             seekAdList[nrOfSeekAds] = NULL;
2523             break;
2524         }
2525 }
2526
2527 Boolean
2528 MatchSoughtLine(char *line)
2529 {
2530     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2531     int nr, base, inc, u=0; char dummy;
2532
2533     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2534        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2535        (u=1) &&
2536        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2537         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2538         // match: compact and save the line
2539         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2540         return TRUE;
2541     }
2542     return FALSE;
2543 }
2544
2545 int
2546 DrawSeekGraph()
2547 {
2548     int i;
2549     if(!seekGraphUp) return FALSE;
2550     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2551     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2552
2553     DrawSeekBackground(0, 0, w, h);
2554     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2555     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2556     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2557         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2558         yy = h-1-yy;
2559         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2560         if(i%500 == 0) {
2561             char buf[MSG_SIZ];
2562             snprintf(buf, MSG_SIZ, "%d", i);
2563             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2564         }
2565     }
2566     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2567     for(i=1; i<100; i+=(i<10?1:5)) {
2568         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2569         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2570         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2571             char buf[MSG_SIZ];
2572             snprintf(buf, MSG_SIZ, "%d", i);
2573             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2574         }
2575     }
2576     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2577     return TRUE;
2578 }
2579
2580 int SeekGraphClick(ClickType click, int x, int y, int moving)
2581 {
2582     static int lastDown = 0, displayed = 0, lastSecond;
2583     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2584         if(click == Release || moving) return FALSE;
2585         nrOfSeekAds = 0;
2586         soughtPending = TRUE;
2587         SendToICS(ics_prefix);
2588         SendToICS("sought\n"); // should this be "sought all"?
2589     } else { // issue challenge based on clicked ad
2590         int dist = 10000; int i, closest = 0, second = 0;
2591         for(i=0; i<nrOfSeekAds; i++) {
2592             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2593             if(d < dist) { dist = d; closest = i; }
2594             second += (d - zList[i] < 120); // count in-range ads
2595             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2596         }
2597         if(dist < 120) {
2598             char buf[MSG_SIZ];
2599             second = (second > 1);
2600             if(displayed != closest || second != lastSecond) {
2601                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2602                 lastSecond = second; displayed = closest;
2603             }
2604             if(click == Press) {
2605                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2606                 lastDown = closest;
2607                 return TRUE;
2608             } // on press 'hit', only show info
2609             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2610             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2611             SendToICS(ics_prefix);
2612             SendToICS(buf);
2613             return TRUE; // let incoming board of started game pop down the graph
2614         } else if(click == Release) { // release 'miss' is ignored
2615             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2616             if(moving == 2) { // right up-click
2617                 nrOfSeekAds = 0; // refresh graph
2618                 soughtPending = TRUE;
2619                 SendToICS(ics_prefix);
2620                 SendToICS("sought\n"); // should this be "sought all"?
2621             }
2622             return TRUE;
2623         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2624         // press miss or release hit 'pop down' seek graph
2625         seekGraphUp = FALSE;
2626         DrawPosition(TRUE, NULL);
2627     }
2628     return TRUE;
2629 }
2630
2631 void
2632 read_from_ics(isr, closure, data, count, error)
2633      InputSourceRef isr;
2634      VOIDSTAR closure;
2635      char *data;
2636      int count;
2637      int error;
2638 {
2639 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2640 #define STARTED_NONE 0
2641 #define STARTED_MOVES 1
2642 #define STARTED_BOARD 2
2643 #define STARTED_OBSERVE 3
2644 #define STARTED_HOLDINGS 4
2645 #define STARTED_CHATTER 5
2646 #define STARTED_COMMENT 6
2647 #define STARTED_MOVES_NOHIDE 7
2648
2649     static int started = STARTED_NONE;
2650     static char parse[20000];
2651     static int parse_pos = 0;
2652     static char buf[BUF_SIZE + 1];
2653     static int firstTime = TRUE, intfSet = FALSE;
2654     static ColorClass prevColor = ColorNormal;
2655     static int savingComment = FALSE;
2656     static int cmatch = 0; // continuation sequence match
2657     char *bp;
2658     char str[MSG_SIZ];
2659     int i, oldi;
2660     int buf_len;
2661     int next_out;
2662     int tkind;
2663     int backup;    /* [DM] For zippy color lines */
2664     char *p;
2665     char talker[MSG_SIZ]; // [HGM] chat
2666     int channel;
2667
2668     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2669
2670     if (appData.debugMode) {
2671       if (!error) {
2672         fprintf(debugFP, "<ICS: ");
2673         show_bytes(debugFP, data, count);
2674         fprintf(debugFP, "\n");
2675       }
2676     }
2677
2678     if (appData.debugMode) { int f = forwardMostMove;
2679         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2680                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2681                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2682     }
2683     if (count > 0) {
2684         /* If last read ended with a partial line that we couldn't parse,
2685            prepend it to the new read and try again. */
2686         if (leftover_len > 0) {
2687             for (i=0; i<leftover_len; i++)
2688               buf[i] = buf[leftover_start + i];
2689         }
2690
2691     /* copy new characters into the buffer */
2692     bp = buf + leftover_len;
2693     buf_len=leftover_len;
2694     for (i=0; i<count; i++)
2695     {
2696         // ignore these
2697         if (data[i] == '\r')
2698             continue;
2699
2700         // join lines split by ICS?
2701         if (!appData.noJoin)
2702         {
2703             /*
2704                 Joining just consists of finding matches against the
2705                 continuation sequence, and discarding that sequence
2706                 if found instead of copying it.  So, until a match
2707                 fails, there's nothing to do since it might be the
2708                 complete sequence, and thus, something we don't want
2709                 copied.
2710             */
2711             if (data[i] == cont_seq[cmatch])
2712             {
2713                 cmatch++;
2714                 if (cmatch == strlen(cont_seq))
2715                 {
2716                     cmatch = 0; // complete match.  just reset the counter
2717
2718                     /*
2719                         it's possible for the ICS to not include the space
2720                         at the end of the last word, making our [correct]
2721                         join operation fuse two separate words.  the server
2722                         does this when the space occurs at the width setting.
2723                     */
2724                     if (!buf_len || buf[buf_len-1] != ' ')
2725                     {
2726                         *bp++ = ' ';
2727                         buf_len++;
2728                     }
2729                 }
2730                 continue;
2731             }
2732             else if (cmatch)
2733             {
2734                 /*
2735                     match failed, so we have to copy what matched before
2736                     falling through and copying this character.  In reality,
2737                     this will only ever be just the newline character, but
2738                     it doesn't hurt to be precise.
2739                 */
2740                 strncpy(bp, cont_seq, cmatch);
2741                 bp += cmatch;
2742                 buf_len += cmatch;
2743                 cmatch = 0;
2744             }
2745         }
2746
2747         // copy this char
2748         *bp++ = data[i];
2749         buf_len++;
2750     }
2751
2752         buf[buf_len] = NULLCHAR;
2753 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2754         next_out = 0;
2755         leftover_start = 0;
2756
2757         i = 0;
2758         while (i < buf_len) {
2759             /* Deal with part of the TELNET option negotiation
2760                protocol.  We refuse to do anything beyond the
2761                defaults, except that we allow the WILL ECHO option,
2762                which ICS uses to turn off password echoing when we are
2763                directly connected to it.  We reject this option
2764                if localLineEditing mode is on (always on in xboard)
2765                and we are talking to port 23, which might be a real
2766                telnet server that will try to keep WILL ECHO on permanently.
2767              */
2768             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2769                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2770                 unsigned char option;
2771                 oldi = i;
2772                 switch ((unsigned char) buf[++i]) {
2773                   case TN_WILL:
2774                     if (appData.debugMode)
2775                       fprintf(debugFP, "\n<WILL ");
2776                     switch (option = (unsigned char) buf[++i]) {
2777                       case TN_ECHO:
2778                         if (appData.debugMode)
2779                           fprintf(debugFP, "ECHO ");
2780                         /* Reply only if this is a change, according
2781                            to the protocol rules. */
2782                         if (remoteEchoOption) break;
2783                         if (appData.localLineEditing &&
2784                             atoi(appData.icsPort) == TN_PORT) {
2785                             TelnetRequest(TN_DONT, TN_ECHO);
2786                         } else {
2787                             EchoOff();
2788                             TelnetRequest(TN_DO, TN_ECHO);
2789                             remoteEchoOption = TRUE;
2790                         }
2791                         break;
2792                       default:
2793                         if (appData.debugMode)
2794                           fprintf(debugFP, "%d ", option);
2795                         /* Whatever this is, we don't want it. */
2796                         TelnetRequest(TN_DONT, option);
2797                         break;
2798                     }
2799                     break;
2800                   case TN_WONT:
2801                     if (appData.debugMode)
2802                       fprintf(debugFP, "\n<WONT ");
2803                     switch (option = (unsigned char) buf[++i]) {
2804                       case TN_ECHO:
2805                         if (appData.debugMode)
2806                           fprintf(debugFP, "ECHO ");
2807                         /* Reply only if this is a change, according
2808                            to the protocol rules. */
2809                         if (!remoteEchoOption) break;
2810                         EchoOn();
2811                         TelnetRequest(TN_DONT, TN_ECHO);
2812                         remoteEchoOption = FALSE;
2813                         break;
2814                       default:
2815                         if (appData.debugMode)
2816                           fprintf(debugFP, "%d ", (unsigned char) option);
2817                         /* Whatever this is, it must already be turned
2818                            off, because we never agree to turn on
2819                            anything non-default, so according to the
2820                            protocol rules, we don't reply. */
2821                         break;
2822                     }
2823                     break;
2824                   case TN_DO:
2825                     if (appData.debugMode)
2826                       fprintf(debugFP, "\n<DO ");
2827                     switch (option = (unsigned char) buf[++i]) {
2828                       default:
2829                         /* Whatever this is, we refuse to do it. */
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "%d ", option);
2832                         TelnetRequest(TN_WONT, option);
2833                         break;
2834                     }
2835                     break;
2836                   case TN_DONT:
2837                     if (appData.debugMode)
2838                       fprintf(debugFP, "\n<DONT ");
2839                     switch (option = (unsigned char) buf[++i]) {
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", option);
2843                         /* Whatever this is, we are already not doing
2844                            it, because we never agree to do anything
2845                            non-default, so according to the protocol
2846                            rules, we don't reply. */
2847                         break;
2848                     }
2849                     break;
2850                   case TN_IAC:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<IAC ");
2853                     /* Doubled IAC; pass it through */
2854                     i--;
2855                     break;
2856                   default:
2857                     if (appData.debugMode)
2858                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2859                     /* Drop all other telnet commands on the floor */
2860                     break;
2861                 }
2862                 if (oldi > next_out)
2863                   SendToPlayer(&buf[next_out], oldi - next_out);
2864                 if (++i > next_out)
2865                   next_out = i;
2866                 continue;
2867             }
2868
2869             /* OK, this at least will *usually* work */
2870             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2871                 loggedOn = TRUE;
2872             }
2873
2874             if (loggedOn && !intfSet) {
2875                 if (ics_type == ICS_ICC) {
2876                   snprintf(str, MSG_SIZ,
2877                           "/set-quietly interface %s\n/set-quietly style 12\n",
2878                           programVersion);
2879                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2880                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2881                 } else if (ics_type == ICS_CHESSNET) {
2882                   snprintf(str, MSG_SIZ, "/style 12\n");
2883                 } else {
2884                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2885                   strcat(str, programVersion);
2886                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2887                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2888                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2889 #ifdef WIN32
2890                   strcat(str, "$iset nohighlight 1\n");
2891 #endif
2892                   strcat(str, "$iset lock 1\n$style 12\n");
2893                 }
2894                 SendToICS(str);
2895                 NotifyFrontendLogin();
2896                 intfSet = TRUE;
2897             }
2898
2899             if (started == STARTED_COMMENT) {
2900                 /* Accumulate characters in comment */
2901                 parse[parse_pos++] = buf[i];
2902                 if (buf[i] == '\n') {
2903                     parse[parse_pos] = NULLCHAR;
2904                     if(chattingPartner>=0) {
2905                         char mess[MSG_SIZ];
2906                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2907                         OutputChatMessage(chattingPartner, mess);
2908                         chattingPartner = -1;
2909                         next_out = i+1; // [HGM] suppress printing in ICS window
2910                     } else
2911                     if(!suppressKibitz) // [HGM] kibitz
2912                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2913                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2914                         int nrDigit = 0, nrAlph = 0, j;
2915                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2916                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2917                         parse[parse_pos] = NULLCHAR;
2918                         // try to be smart: if it does not look like search info, it should go to
2919                         // ICS interaction window after all, not to engine-output window.
2920                         for(j=0; j<parse_pos; j++) { // count letters and digits
2921                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2922                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2923                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2924                         }
2925                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2926                             int depth=0; float score;
2927                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2928                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2929                                 pvInfoList[forwardMostMove-1].depth = depth;
2930                                 pvInfoList[forwardMostMove-1].score = 100*score;
2931                             }
2932                             OutputKibitz(suppressKibitz, parse);
2933                         } else {
2934                             char tmp[MSG_SIZ];
2935                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2936                             SendToPlayer(tmp, strlen(tmp));
2937                         }
2938                         next_out = i+1; // [HGM] suppress printing in ICS window
2939                     }
2940                     started = STARTED_NONE;
2941                 } else {
2942                     /* Don't match patterns against characters in comment */
2943                     i++;
2944                     continue;
2945                 }
2946             }
2947             if (started == STARTED_CHATTER) {
2948                 if (buf[i] != '\n') {
2949                     /* Don't match patterns against characters in chatter */
2950                     i++;
2951                     continue;
2952                 }
2953                 started = STARTED_NONE;
2954                 if(suppressKibitz) next_out = i+1;
2955             }
2956
2957             /* Kludge to deal with rcmd protocol */
2958             if (firstTime && looking_at(buf, &i, "\001*")) {
2959                 DisplayFatalError(&buf[1], 0, 1);
2960                 continue;
2961             } else {
2962                 firstTime = FALSE;
2963             }
2964
2965             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2966                 ics_type = ICS_ICC;
2967                 ics_prefix = "/";
2968                 if (appData.debugMode)
2969                   fprintf(debugFP, "ics_type %d\n", ics_type);
2970                 continue;
2971             }
2972             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2973                 ics_type = ICS_FICS;
2974                 ics_prefix = "$";
2975                 if (appData.debugMode)
2976                   fprintf(debugFP, "ics_type %d\n", ics_type);
2977                 continue;
2978             }
2979             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2980                 ics_type = ICS_CHESSNET;
2981                 ics_prefix = "/";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986
2987             if (!loggedOn &&
2988                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2989                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2990                  looking_at(buf, &i, "will be \"*\""))) {
2991               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2992               continue;
2993             }
2994
2995             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2996               char buf[MSG_SIZ];
2997               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2998               DisplayIcsInteractionTitle(buf);
2999               have_set_title = TRUE;
3000             }
3001
3002             /* skip finger notes */
3003             if (started == STARTED_NONE &&
3004                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3005                  (buf[i] == '1' && buf[i+1] == '0')) &&
3006                 buf[i+2] == ':' && buf[i+3] == ' ') {
3007               started = STARTED_CHATTER;
3008               i += 3;
3009               continue;
3010             }
3011
3012             oldi = i;
3013             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3014             if(appData.seekGraph) {
3015                 if(soughtPending && MatchSoughtLine(buf+i)) {
3016                     i = strstr(buf+i, "rated") - buf;
3017                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018                     next_out = leftover_start = i;
3019                     started = STARTED_CHATTER;
3020                     suppressKibitz = TRUE;
3021                     continue;
3022                 }
3023                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3024                         && looking_at(buf, &i, "* ads displayed")) {
3025                     soughtPending = FALSE;
3026                     seekGraphUp = TRUE;
3027                     DrawSeekGraph();
3028                     continue;
3029                 }
3030                 if(appData.autoRefresh) {
3031                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3032                         int s = (ics_type == ICS_ICC); // ICC format differs
3033                         if(seekGraphUp)
3034                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3035                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3036                         looking_at(buf, &i, "*% "); // eat prompt
3037                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3038                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039                         next_out = i; // suppress
3040                         continue;
3041                     }
3042                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3043                         char *p = star_match[0];
3044                         while(*p) {
3045                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3046                             while(*p && *p++ != ' '); // next
3047                         }
3048                         looking_at(buf, &i, "*% "); // eat prompt
3049                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                         next_out = i;
3051                         continue;
3052                     }
3053                 }
3054             }
3055
3056             /* skip formula vars */
3057             if (started == STARTED_NONE &&
3058                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3059               started = STARTED_CHATTER;
3060               i += 3;
3061               continue;
3062             }
3063
3064             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3065             if (appData.autoKibitz && started == STARTED_NONE &&
3066                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3067                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3068                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3069                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3070                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3071                         suppressKibitz = TRUE;
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i;
3074                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3075                                 && (gameMode == IcsPlayingWhite)) ||
3076                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3077                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3078                             started = STARTED_CHATTER; // own kibitz we simply discard
3079                         else {
3080                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3081                             parse_pos = 0; parse[0] = NULLCHAR;
3082                             savingComment = TRUE;
3083                             suppressKibitz = gameMode != IcsObserving ? 2 :
3084                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3085                         }
3086                         continue;
3087                 } else
3088                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3089                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3090                          && atoi(star_match[0])) {
3091                     // suppress the acknowledgements of our own autoKibitz
3092                     char *p;
3093                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3094                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3095                     SendToPlayer(star_match[0], strlen(star_match[0]));
3096                     if(looking_at(buf, &i, "*% ")) // eat prompt
3097                         suppressKibitz = FALSE;
3098                     next_out = i;
3099                     continue;
3100                 }
3101             } // [HGM] kibitz: end of patch
3102
3103             // [HGM] chat: intercept tells by users for which we have an open chat window
3104             channel = -1;
3105             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3106                                            looking_at(buf, &i, "* whispers:") ||
3107                                            looking_at(buf, &i, "* kibitzes:") ||
3108                                            looking_at(buf, &i, "* shouts:") ||
3109                                            looking_at(buf, &i, "* c-shouts:") ||
3110                                            looking_at(buf, &i, "--> * ") ||
3111                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3112                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3113                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3114                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3115                 int p;
3116                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3117                 chattingPartner = -1;
3118
3119                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3120                 for(p=0; p<MAX_CHAT; p++) {
3121                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3122                     talker[0] = '['; strcat(talker, "] ");
3123                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3124                     chattingPartner = p; break;
3125                     }
3126                 } else
3127                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3128                 for(p=0; p<MAX_CHAT; p++) {
3129                     if(!strcmp("kibitzes", chatPartner[p])) {
3130                         talker[0] = '['; strcat(talker, "] ");
3131                         chattingPartner = p; break;
3132                     }
3133                 } else
3134                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3135                 for(p=0; p<MAX_CHAT; p++) {
3136                     if(!strcmp("whispers", chatPartner[p])) {
3137                         talker[0] = '['; strcat(talker, "] ");
3138                         chattingPartner = p; break;
3139                     }
3140                 } else
3141                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3142                   if(buf[i-8] == '-' && buf[i-3] == 't')
3143                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3144                     if(!strcmp("c-shouts", chatPartner[p])) {
3145                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3146                         chattingPartner = p; break;
3147                     }
3148                   }
3149                   if(chattingPartner < 0)
3150                   for(p=0; p<MAX_CHAT; p++) {
3151                     if(!strcmp("shouts", chatPartner[p])) {
3152                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3153                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3154                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3155                         chattingPartner = p; break;
3156                     }
3157                   }
3158                 }
3159                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3160                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3161                     talker[0] = 0; Colorize(ColorTell, FALSE);
3162                     chattingPartner = p; break;
3163                 }
3164                 if(chattingPartner<0) i = oldi; else {
3165                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3166                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3167                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3168                     started = STARTED_COMMENT;
3169                     parse_pos = 0; parse[0] = NULLCHAR;
3170                     savingComment = 3 + chattingPartner; // counts as TRUE
3171                     suppressKibitz = TRUE;
3172                     continue;
3173                 }
3174             } // [HGM] chat: end of patch
3175
3176           backup = i;
3177             if (appData.zippyTalk || appData.zippyPlay) {
3178                 /* [DM] Backup address for color zippy lines */
3179 #if ZIPPY
3180                if (loggedOn == TRUE)
3181                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3182                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3183 #endif
3184             } // [DM] 'else { ' deleted
3185                 if (
3186                     /* Regular tells and says */
3187                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3188                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3189                     looking_at(buf, &i, "* says: ") ||
3190                     /* Don't color "message" or "messages" output */
3191                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3192                     looking_at(buf, &i, "*. * at *:*: ") ||
3193                     looking_at(buf, &i, "--* (*:*): ") ||
3194                     /* Message notifications (same color as tells) */
3195                     looking_at(buf, &i, "* has left a message ") ||
3196                     looking_at(buf, &i, "* just sent you a message:\n") ||
3197                     /* Whispers and kibitzes */
3198                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3199                     looking_at(buf, &i, "* kibitzes: ") ||
3200                     /* Channel tells */
3201                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3202
3203                   if (tkind == 1 && strchr(star_match[0], ':')) {
3204                       /* Avoid "tells you:" spoofs in channels */
3205                      tkind = 3;
3206                   }
3207                   if (star_match[0][0] == NULLCHAR ||
3208                       strchr(star_match[0], ' ') ||
3209                       (tkind == 3 && strchr(star_match[1], ' '))) {
3210                     /* Reject bogus matches */
3211                     i = oldi;
3212                   } else {
3213                     if (appData.colorize) {
3214                       if (oldi > next_out) {
3215                         SendToPlayer(&buf[next_out], oldi - next_out);
3216                         next_out = oldi;
3217                       }
3218                       switch (tkind) {
3219                       case 1:
3220                         Colorize(ColorTell, FALSE);
3221                         curColor = ColorTell;
3222                         break;
3223                       case 2:
3224                         Colorize(ColorKibitz, FALSE);
3225                         curColor = ColorKibitz;
3226                         break;
3227                       case 3:
3228                         p = strrchr(star_match[1], '(');
3229                         if (p == NULL) {
3230                           p = star_match[1];
3231                         } else {
3232                           p++;
3233                         }
3234                         if (atoi(p) == 1) {
3235                           Colorize(ColorChannel1, FALSE);
3236                           curColor = ColorChannel1;
3237                         } else {
3238                           Colorize(ColorChannel, FALSE);
3239                           curColor = ColorChannel;
3240                         }
3241                         break;
3242                       case 5:
3243                         curColor = ColorNormal;
3244                         break;
3245                       }
3246                     }
3247                     if (started == STARTED_NONE && appData.autoComment &&
3248                         (gameMode == IcsObserving ||
3249                          gameMode == IcsPlayingWhite ||
3250                          gameMode == IcsPlayingBlack)) {
3251                       parse_pos = i - oldi;
3252                       memcpy(parse, &buf[oldi], parse_pos);
3253                       parse[parse_pos] = NULLCHAR;
3254                       started = STARTED_COMMENT;
3255                       savingComment = TRUE;
3256                     } else {
3257                       started = STARTED_CHATTER;
3258                       savingComment = FALSE;
3259                     }
3260                     loggedOn = TRUE;
3261                     continue;
3262                   }
3263                 }
3264
3265                 if (looking_at(buf, &i, "* s-shouts: ") ||
3266                     looking_at(buf, &i, "* c-shouts: ")) {
3267                     if (appData.colorize) {
3268                         if (oldi > next_out) {
3269                             SendToPlayer(&buf[next_out], oldi - next_out);
3270                             next_out = oldi;
3271                         }
3272                         Colorize(ColorSShout, FALSE);
3273                         curColor = ColorSShout;
3274                     }
3275                     loggedOn = TRUE;
3276                     started = STARTED_CHATTER;
3277                     continue;
3278                 }
3279
3280                 if (looking_at(buf, &i, "--->")) {
3281                     loggedOn = TRUE;
3282                     continue;
3283                 }
3284
3285                 if (looking_at(buf, &i, "* shouts: ") ||
3286                     looking_at(buf, &i, "--> ")) {
3287                     if (appData.colorize) {
3288                         if (oldi > next_out) {
3289                             SendToPlayer(&buf[next_out], oldi - next_out);
3290                             next_out = oldi;
3291                         }
3292                         Colorize(ColorShout, FALSE);
3293                         curColor = ColorShout;
3294                     }
3295                     loggedOn = TRUE;
3296                     started = STARTED_CHATTER;
3297                     continue;
3298                 }
3299
3300                 if (looking_at( buf, &i, "Challenge:")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorChallenge, FALSE);
3307                         curColor = ColorChallenge;
3308                     }
3309                     loggedOn = TRUE;
3310                     continue;
3311                 }
3312
3313                 if (looking_at(buf, &i, "* offers you") ||
3314                     looking_at(buf, &i, "* offers to be") ||
3315                     looking_at(buf, &i, "* would like to") ||
3316                     looking_at(buf, &i, "* requests to") ||
3317                     looking_at(buf, &i, "Your opponent offers") ||
3318                     looking_at(buf, &i, "Your opponent requests")) {
3319
3320                     if (appData.colorize) {
3321                         if (oldi > next_out) {
3322                             SendToPlayer(&buf[next_out], oldi - next_out);
3323                             next_out = oldi;
3324                         }
3325                         Colorize(ColorRequest, FALSE);
3326                         curColor = ColorRequest;
3327                     }
3328                     continue;
3329                 }
3330
3331                 if (looking_at(buf, &i, "* (*) seeking")) {
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorSeek, FALSE);
3338                         curColor = ColorSeek;
3339                     }
3340                     continue;
3341             }
3342
3343           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3344
3345             if (looking_at(buf, &i, "\\   ")) {
3346                 if (prevColor != ColorNormal) {
3347                     if (oldi > next_out) {
3348                         SendToPlayer(&buf[next_out], oldi - next_out);
3349                         next_out = oldi;
3350                     }
3351                     Colorize(prevColor, TRUE);
3352                     curColor = prevColor;
3353                 }
3354                 if (savingComment) {
3355                     parse_pos = i - oldi;
3356                     memcpy(parse, &buf[oldi], parse_pos);
3357                     parse[parse_pos] = NULLCHAR;
3358                     started = STARTED_COMMENT;
3359                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3360                         chattingPartner = savingComment - 3; // kludge to remember the box
3361                 } else {
3362                     started = STARTED_CHATTER;
3363                 }
3364                 continue;
3365             }
3366
3367             if (looking_at(buf, &i, "Black Strength :") ||
3368                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3369                 looking_at(buf, &i, "<10>") ||
3370                 looking_at(buf, &i, "#@#")) {
3371                 /* Wrong board style */
3372                 loggedOn = TRUE;
3373                 SendToICS(ics_prefix);
3374                 SendToICS("set style 12\n");
3375                 SendToICS(ics_prefix);
3376                 SendToICS("refresh\n");
3377                 continue;
3378             }
3379
3380             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3381                 ICSInitScript();
3382                 have_sent_ICS_logon = 1;
3383                 continue;
3384             }
3385
3386             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3387                 (looking_at(buf, &i, "\n<12> ") ||
3388                  looking_at(buf, &i, "<12> "))) {
3389                 loggedOn = TRUE;
3390                 if (oldi > next_out) {
3391                     SendToPlayer(&buf[next_out], oldi - next_out);
3392                 }
3393                 next_out = i;
3394                 started = STARTED_BOARD;
3395                 parse_pos = 0;
3396                 continue;
3397             }
3398
3399             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3400                 looking_at(buf, &i, "<b1> ")) {
3401                 if (oldi > next_out) {
3402                     SendToPlayer(&buf[next_out], oldi - next_out);
3403                 }
3404                 next_out = i;
3405                 started = STARTED_HOLDINGS;
3406                 parse_pos = 0;
3407                 continue;
3408             }
3409
3410             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3411                 loggedOn = TRUE;
3412                 /* Header for a move list -- first line */
3413
3414                 switch (ics_getting_history) {
3415                   case H_FALSE:
3416                     switch (gameMode) {
3417                       case IcsIdle:
3418                       case BeginningOfGame:
3419                         /* User typed "moves" or "oldmoves" while we
3420                            were idle.  Pretend we asked for these
3421                            moves and soak them up so user can step
3422                            through them and/or save them.
3423                            */
3424                         Reset(FALSE, TRUE);
3425                         gameMode = IcsObserving;
3426                         ModeHighlight();
3427                         ics_gamenum = -1;
3428                         ics_getting_history = H_GOT_UNREQ_HEADER;
3429                         break;
3430                       case EditGame: /*?*/
3431                       case EditPosition: /*?*/
3432                         /* Should above feature work in these modes too? */
3433                         /* For now it doesn't */
3434                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3435                         break;
3436                       default:
3437                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3438                         break;
3439                     }
3440                     break;
3441                   case H_REQUESTED:
3442                     /* Is this the right one? */
3443                     if (gameInfo.white && gameInfo.black &&
3444                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3445                         strcmp(gameInfo.black, star_match[2]) == 0) {
3446                         /* All is well */
3447                         ics_getting_history = H_GOT_REQ_HEADER;
3448                     }
3449                     break;
3450                   case H_GOT_REQ_HEADER:
3451                   case H_GOT_UNREQ_HEADER:
3452                   case H_GOT_UNWANTED_HEADER:
3453                   case H_GETTING_MOVES:
3454                     /* Should not happen */
3455                     DisplayError(_("Error gathering move list: two headers"), 0);
3456                     ics_getting_history = H_FALSE;
3457                     break;
3458                 }
3459
3460                 /* Save player ratings into gameInfo if needed */
3461                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3462                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3463                     (gameInfo.whiteRating == -1 ||
3464                      gameInfo.blackRating == -1)) {
3465
3466                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3467                     gameInfo.blackRating = string_to_rating(star_match[3]);
3468                     if (appData.debugMode)
3469                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3470                               gameInfo.whiteRating, gameInfo.blackRating);
3471                 }
3472                 continue;
3473             }
3474
3475             if (looking_at(buf, &i,
3476               "* * match, initial time: * minute*, increment: * second")) {
3477                 /* Header for a move list -- second line */
3478                 /* Initial board will follow if this is a wild game */
3479                 if (gameInfo.event != NULL) free(gameInfo.event);
3480                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3481                 gameInfo.event = StrSave(str);
3482                 /* [HGM] we switched variant. Translate boards if needed. */
3483                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3484                 continue;
3485             }
3486
3487             if (looking_at(buf, &i, "Move  ")) {
3488                 /* Beginning of a move list */
3489                 switch (ics_getting_history) {
3490                   case H_FALSE:
3491                     /* Normally should not happen */
3492                     /* Maybe user hit reset while we were parsing */
3493                     break;
3494                   case H_REQUESTED:
3495                     /* Happens if we are ignoring a move list that is not
3496                      * the one we just requested.  Common if the user
3497                      * tries to observe two games without turning off
3498                      * getMoveList */
3499                     break;
3500                   case H_GETTING_MOVES:
3501                     /* Should not happen */
3502                     DisplayError(_("Error gathering move list: nested"), 0);
3503                     ics_getting_history = H_FALSE;
3504                     break;
3505                   case H_GOT_REQ_HEADER:
3506                     ics_getting_history = H_GETTING_MOVES;
3507                     started = STARTED_MOVES;
3508                     parse_pos = 0;
3509                     if (oldi > next_out) {
3510                         SendToPlayer(&buf[next_out], oldi - next_out);
3511                     }
3512                     break;
3513                   case H_GOT_UNREQ_HEADER:
3514                     ics_getting_history = H_GETTING_MOVES;
3515                     started = STARTED_MOVES_NOHIDE;
3516                     parse_pos = 0;
3517                     break;
3518                   case H_GOT_UNWANTED_HEADER:
3519                     ics_getting_history = H_FALSE;
3520                     break;
3521                 }
3522                 continue;
3523             }
3524
3525             if (looking_at(buf, &i, "% ") ||
3526                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3527                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3528                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3529                     soughtPending = FALSE;
3530                     seekGraphUp = TRUE;
3531                     DrawSeekGraph();
3532                 }
3533                 if(suppressKibitz) next_out = i;
3534                 savingComment = FALSE;
3535                 suppressKibitz = 0;
3536                 switch (started) {
3537                   case STARTED_MOVES:
3538                   case STARTED_MOVES_NOHIDE:
3539                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3540                     parse[parse_pos + i - oldi] = NULLCHAR;
3541                     ParseGameHistory(parse);
3542 #if ZIPPY
3543                     if (appData.zippyPlay && first.initDone) {
3544                         FeedMovesToProgram(&first, forwardMostMove);
3545                         if (gameMode == IcsPlayingWhite) {
3546                             if (WhiteOnMove(forwardMostMove)) {
3547                                 if (first.sendTime) {
3548                                   if (first.useColors) {
3549                                     SendToProgram("black\n", &first);
3550                                   }
3551                                   SendTimeRemaining(&first, TRUE);
3552                                 }
3553                                 if (first.useColors) {
3554                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3555                                 }
3556                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3557                                 first.maybeThinking = TRUE;
3558                             } else {
3559                                 if (first.usePlayother) {
3560                                   if (first.sendTime) {
3561                                     SendTimeRemaining(&first, TRUE);
3562                                   }
3563                                   SendToProgram("playother\n", &first);
3564                                   firstMove = FALSE;
3565                                 } else {
3566                                   firstMove = TRUE;
3567                                 }
3568                             }
3569                         } else if (gameMode == IcsPlayingBlack) {
3570                             if (!WhiteOnMove(forwardMostMove)) {
3571                                 if (first.sendTime) {
3572                                   if (first.useColors) {
3573                                     SendToProgram("white\n", &first);
3574                                   }
3575                                   SendTimeRemaining(&first, FALSE);
3576                                 }
3577                                 if (first.useColors) {
3578                                   SendToProgram("black\n", &first);
3579                                 }
3580                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3581                                 first.maybeThinking = TRUE;
3582                             } else {
3583                                 if (first.usePlayother) {
3584                                   if (first.sendTime) {
3585                                     SendTimeRemaining(&first, FALSE);
3586                                   }
3587                                   SendToProgram("playother\n", &first);
3588                                   firstMove = FALSE;
3589                                 } else {
3590                                   firstMove = TRUE;
3591                                 }
3592                             }
3593                         }
3594                     }
3595 #endif
3596                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3597                         /* Moves came from oldmoves or moves command
3598                            while we weren't doing anything else.
3599                            */
3600                         currentMove = forwardMostMove;
3601                         ClearHighlights();/*!!could figure this out*/
3602                         flipView = appData.flipView;
3603                         DrawPosition(TRUE, boards[currentMove]);
3604                         DisplayBothClocks();
3605                         snprintf(str, MSG_SIZ, "%s vs. %s",
3606                                 gameInfo.white, gameInfo.black);
3607                         DisplayTitle(str);
3608                         gameMode = IcsIdle;
3609                     } else {
3610                         /* Moves were history of an active game */
3611                         if (gameInfo.resultDetails != NULL) {
3612                             free(gameInfo.resultDetails);
3613                             gameInfo.resultDetails = NULL;
3614                         }
3615                     }
3616                     HistorySet(parseList, backwardMostMove,
3617                                forwardMostMove, currentMove-1);
3618                     DisplayMove(currentMove - 1);
3619                     if (started == STARTED_MOVES) next_out = i;
3620                     started = STARTED_NONE;
3621                     ics_getting_history = H_FALSE;
3622                     break;
3623
3624                   case STARTED_OBSERVE:
3625                     started = STARTED_NONE;
3626                     SendToICS(ics_prefix);
3627                     SendToICS("refresh\n");
3628                     break;
3629
3630                   default:
3631                     break;
3632                 }
3633                 if(bookHit) { // [HGM] book: simulate book reply
3634                     static char bookMove[MSG_SIZ]; // a bit generous?
3635
3636                     programStats.nodes = programStats.depth = programStats.time =
3637                     programStats.score = programStats.got_only_move = 0;
3638                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3639
3640                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3641                     strcat(bookMove, bookHit);
3642                     HandleMachineMove(bookMove, &first);
3643                 }
3644                 continue;
3645             }
3646
3647             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3648                  started == STARTED_HOLDINGS ||
3649                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3650                 /* Accumulate characters in move list or board */
3651                 parse[parse_pos++] = buf[i];
3652             }
3653
3654             /* Start of game messages.  Mostly we detect start of game
3655                when the first board image arrives.  On some versions
3656                of the ICS, though, we need to do a "refresh" after starting
3657                to observe in order to get the current board right away. */
3658             if (looking_at(buf, &i, "Adding game * to observation list")) {
3659                 started = STARTED_OBSERVE;
3660                 continue;
3661             }
3662
3663             /* Handle auto-observe */
3664             if (appData.autoObserve &&
3665                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3666                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3667                 char *player;
3668                 /* Choose the player that was highlighted, if any. */
3669                 if (star_match[0][0] == '\033' ||
3670                     star_match[1][0] != '\033') {
3671                     player = star_match[0];
3672                 } else {
3673                     player = star_match[2];
3674                 }
3675                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3676                         ics_prefix, StripHighlightAndTitle(player));
3677                 SendToICS(str);
3678
3679                 /* Save ratings from notify string */
3680                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3681                 player1Rating = string_to_rating(star_match[1]);
3682                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3683                 player2Rating = string_to_rating(star_match[3]);
3684
3685                 if (appData.debugMode)
3686                   fprintf(debugFP,
3687                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3688                           player1Name, player1Rating,
3689                           player2Name, player2Rating);
3690
3691                 continue;
3692             }
3693
3694             /* Deal with automatic examine mode after a game,
3695                and with IcsObserving -> IcsExamining transition */
3696             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3697                 looking_at(buf, &i, "has made you an examiner of game *")) {
3698
3699                 int gamenum = atoi(star_match[0]);
3700                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3701                     gamenum == ics_gamenum) {
3702                     /* We were already playing or observing this game;
3703                        no need to refetch history */
3704                     gameMode = IcsExamining;
3705                     if (pausing) {
3706                         pauseExamForwardMostMove = forwardMostMove;
3707                     } else if (currentMove < forwardMostMove) {
3708                         ForwardInner(forwardMostMove);
3709                     }
3710                 } else {
3711                     /* I don't think this case really can happen */
3712                     SendToICS(ics_prefix);
3713                     SendToICS("refresh\n");
3714                 }
3715                 continue;
3716             }
3717
3718             /* Error messages */
3719 //          if (ics_user_moved) {
3720             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3721                 if (looking_at(buf, &i, "Illegal move") ||
3722                     looking_at(buf, &i, "Not a legal move") ||
3723                     looking_at(buf, &i, "Your king is in check") ||
3724                     looking_at(buf, &i, "It isn't your turn") ||
3725                     looking_at(buf, &i, "It is not your move")) {
3726                     /* Illegal move */
3727                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3728                         currentMove = forwardMostMove-1;
3729                         DisplayMove(currentMove - 1); /* before DMError */
3730                         DrawPosition(FALSE, boards[currentMove]);
3731                         SwitchClocks(forwardMostMove-1); // [HGM] race
3732                         DisplayBothClocks();
3733                     }
3734                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3735                     ics_user_moved = 0;
3736                     continue;
3737                 }
3738             }
3739
3740             if (looking_at(buf, &i, "still have time") ||
3741                 looking_at(buf, &i, "not out of time") ||
3742                 looking_at(buf, &i, "either player is out of time") ||
3743                 looking_at(buf, &i, "has timeseal; checking")) {
3744                 /* We must have called his flag a little too soon */
3745                 whiteFlag = blackFlag = FALSE;
3746                 continue;
3747             }
3748
3749             if (looking_at(buf, &i, "added * seconds to") ||
3750                 looking_at(buf, &i, "seconds were added to")) {
3751                 /* Update the clocks */
3752                 SendToICS(ics_prefix);
3753                 SendToICS("refresh\n");
3754                 continue;
3755             }
3756
3757             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3758                 ics_clock_paused = TRUE;
3759                 StopClocks();
3760                 continue;
3761             }
3762
3763             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3764                 ics_clock_paused = FALSE;
3765                 StartClocks();
3766                 continue;
3767             }
3768
3769             /* Grab player ratings from the Creating: message.
3770                Note we have to check for the special case when
3771                the ICS inserts things like [white] or [black]. */
3772             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3773                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3774                 /* star_matches:
3775                    0    player 1 name (not necessarily white)
3776                    1    player 1 rating
3777                    2    empty, white, or black (IGNORED)
3778                    3    player 2 name (not necessarily black)
3779                    4    player 2 rating
3780
3781                    The names/ratings are sorted out when the game
3782                    actually starts (below).
3783                 */
3784                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3785                 player1Rating = string_to_rating(star_match[1]);
3786                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3787                 player2Rating = string_to_rating(star_match[4]);
3788
3789                 if (appData.debugMode)
3790                   fprintf(debugFP,
3791                           "Ratings from 'Creating:' %s %d, %s %d\n",
3792                           player1Name, player1Rating,
3793                           player2Name, player2Rating);
3794
3795                 continue;
3796             }
3797
3798             /* Improved generic start/end-of-game messages */
3799             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3800                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3801                 /* If tkind == 0: */
3802                 /* star_match[0] is the game number */
3803                 /*           [1] is the white player's name */
3804                 /*           [2] is the black player's name */
3805                 /* For end-of-game: */
3806                 /*           [3] is the reason for the game end */
3807                 /*           [4] is a PGN end game-token, preceded by " " */
3808                 /* For start-of-game: */
3809                 /*           [3] begins with "Creating" or "Continuing" */
3810                 /*           [4] is " *" or empty (don't care). */
3811                 int gamenum = atoi(star_match[0]);
3812                 char *whitename, *blackname, *why, *endtoken;
3813                 ChessMove endtype = EndOfFile;
3814
3815                 if (tkind == 0) {
3816                   whitename = star_match[1];
3817                   blackname = star_match[2];
3818                   why = star_match[3];
3819                   endtoken = star_match[4];
3820                 } else {
3821                   whitename = star_match[1];
3822                   blackname = star_match[3];
3823                   why = star_match[5];
3824                   endtoken = star_match[6];
3825                 }
3826
3827                 /* Game start messages */
3828                 if (strncmp(why, "Creating ", 9) == 0 ||
3829                     strncmp(why, "Continuing ", 11) == 0) {
3830                     gs_gamenum = gamenum;
3831                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3832                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3833 #if ZIPPY
3834                     if (appData.zippyPlay) {
3835                         ZippyGameStart(whitename, blackname);
3836                     }
3837 #endif /*ZIPPY*/
3838                     partnerBoardValid = FALSE; // [HGM] bughouse
3839                     continue;
3840                 }
3841
3842                 /* Game end messages */
3843                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3844                     ics_gamenum != gamenum) {
3845                     continue;
3846                 }
3847                 while (endtoken[0] == ' ') endtoken++;
3848                 switch (endtoken[0]) {
3849                   case '*':
3850                   default:
3851                     endtype = GameUnfinished;
3852                     break;
3853                   case '0':
3854                     endtype = BlackWins;
3855                     break;
3856                   case '1':
3857                     if (endtoken[1] == '/')
3858                       endtype = GameIsDrawn;
3859                     else
3860                       endtype = WhiteWins;
3861                     break;
3862                 }
3863                 GameEnds(endtype, why, GE_ICS);
3864 #if ZIPPY
3865                 if (appData.zippyPlay && first.initDone) {
3866                     ZippyGameEnd(endtype, why);
3867                     if (first.pr == NULL) {
3868                       /* Start the next process early so that we'll
3869                          be ready for the next challenge */
3870                       StartChessProgram(&first);
3871                     }
3872                     /* Send "new" early, in case this command takes
3873                        a long time to finish, so that we'll be ready
3874                        for the next challenge. */
3875                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3876                     Reset(TRUE, TRUE);
3877                 }
3878 #endif /*ZIPPY*/
3879                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3880                 continue;
3881             }
3882
3883             if (looking_at(buf, &i, "Removing game * from observation") ||
3884                 looking_at(buf, &i, "no longer observing game *") ||
3885                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3886                 if (gameMode == IcsObserving &&
3887                     atoi(star_match[0]) == ics_gamenum)
3888                   {
3889                       /* icsEngineAnalyze */
3890                       if (appData.icsEngineAnalyze) {
3891                             ExitAnalyzeMode();
3892                             ModeHighlight();
3893                       }
3894                       StopClocks();
3895                       gameMode = IcsIdle;
3896                       ics_gamenum = -1;
3897                       ics_user_moved = FALSE;
3898                   }
3899                 continue;
3900             }
3901
3902             if (looking_at(buf, &i, "no longer examining game *")) {
3903                 if (gameMode == IcsExamining &&
3904                     atoi(star_match[0]) == ics_gamenum)
3905                   {
3906                       gameMode = IcsIdle;
3907                       ics_gamenum = -1;
3908                       ics_user_moved = FALSE;
3909                   }
3910                 continue;
3911             }
3912
3913             /* Advance leftover_start past any newlines we find,
3914                so only partial lines can get reparsed */
3915             if (looking_at(buf, &i, "\n")) {
3916                 prevColor = curColor;
3917                 if (curColor != ColorNormal) {
3918                     if (oldi > next_out) {
3919                         SendToPlayer(&buf[next_out], oldi - next_out);
3920                         next_out = oldi;
3921                     }
3922                     Colorize(ColorNormal, FALSE);
3923                     curColor = ColorNormal;
3924                 }
3925                 if (started == STARTED_BOARD) {
3926                     started = STARTED_NONE;
3927                     parse[parse_pos] = NULLCHAR;
3928                     ParseBoard12(parse);
3929                     ics_user_moved = 0;
3930
3931                     /* Send premove here */
3932                     if (appData.premove) {
3933                       char str[MSG_SIZ];
3934                       if (currentMove == 0 &&
3935                           gameMode == IcsPlayingWhite &&
3936                           appData.premoveWhite) {
3937                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3938                         if (appData.debugMode)
3939                           fprintf(debugFP, "Sending premove:\n");
3940                         SendToICS(str);
3941                       } else if (currentMove == 1 &&
3942                                  gameMode == IcsPlayingBlack &&
3943                                  appData.premoveBlack) {
3944                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3945                         if (appData.debugMode)
3946                           fprintf(debugFP, "Sending premove:\n");
3947                         SendToICS(str);
3948                       } else if (gotPremove) {
3949                         gotPremove = 0;
3950                         ClearPremoveHighlights();
3951                         if (appData.debugMode)
3952                           fprintf(debugFP, "Sending premove:\n");
3953                           UserMoveEvent(premoveFromX, premoveFromY,
3954                                         premoveToX, premoveToY,
3955                                         premovePromoChar);
3956                       }
3957                     }
3958
3959                     /* Usually suppress following prompt */
3960                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3961                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3962                         if (looking_at(buf, &i, "*% ")) {
3963                             savingComment = FALSE;
3964                             suppressKibitz = 0;
3965                         }
3966                     }
3967                     next_out = i;
3968                 } else if (started == STARTED_HOLDINGS) {
3969                     int gamenum;
3970                     char new_piece[MSG_SIZ];
3971                     started = STARTED_NONE;
3972                     parse[parse_pos] = NULLCHAR;
3973                     if (appData.debugMode)
3974                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3975                                                         parse, currentMove);
3976                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3977                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3978                         if (gameInfo.variant == VariantNormal) {
3979                           /* [HGM] We seem to switch variant during a game!
3980                            * Presumably no holdings were displayed, so we have
3981                            * to move the position two files to the right to
3982                            * create room for them!
3983                            */
3984                           VariantClass newVariant;
3985                           switch(gameInfo.boardWidth) { // base guess on board width
3986                                 case 9:  newVariant = VariantShogi; break;
3987                                 case 10: newVariant = VariantGreat; break;
3988                                 default: newVariant = VariantCrazyhouse; break;
3989                           }
3990                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3991                           /* Get a move list just to see the header, which
3992                              will tell us whether this is really bug or zh */
3993                           if (ics_getting_history == H_FALSE) {
3994                             ics_getting_history = H_REQUESTED;
3995                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3996                             SendToICS(str);
3997                           }
3998                         }
3999                         new_piece[0] = NULLCHAR;
4000                         sscanf(parse, "game %d white [%s black [%s <- %s",
4001                                &gamenum, white_holding, black_holding,
4002                                new_piece);
4003                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4004                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4005                         /* [HGM] copy holdings to board holdings area */
4006                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4007                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4008                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4009 #if ZIPPY
4010                         if (appData.zippyPlay && first.initDone) {
4011                             ZippyHoldings(white_holding, black_holding,
4012                                           new_piece);
4013                         }
4014 #endif /*ZIPPY*/
4015                         if (tinyLayout || smallLayout) {
4016                             char wh[16], bh[16];
4017                             PackHolding(wh, white_holding);
4018                             PackHolding(bh, black_holding);
4019                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4020                                     gameInfo.white, gameInfo.black);
4021                         } else {
4022                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4023                                     gameInfo.white, white_holding,
4024                                     gameInfo.black, black_holding);
4025                         }
4026                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4027                         DrawPosition(FALSE, boards[currentMove]);
4028                         DisplayTitle(str);
4029                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4030                         sscanf(parse, "game %d white [%s black [%s <- %s",
4031                                &gamenum, white_holding, black_holding,
4032                                new_piece);
4033                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4034                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4035                         /* [HGM] copy holdings to partner-board holdings area */
4036                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4037                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4038                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4039                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4040                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4041                       }
4042                     }
4043                     /* Suppress following prompt */
4044                     if (looking_at(buf, &i, "*% ")) {
4045                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4046                         savingComment = FALSE;
4047                         suppressKibitz = 0;
4048                     }
4049                     next_out = i;
4050                 }
4051                 continue;
4052             }
4053
4054             i++;                /* skip unparsed character and loop back */
4055         }
4056
4057         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4058 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4059 //          SendToPlayer(&buf[next_out], i - next_out);
4060             started != STARTED_HOLDINGS && leftover_start > next_out) {
4061             SendToPlayer(&buf[next_out], leftover_start - next_out);
4062             next_out = i;
4063         }
4064
4065         leftover_len = buf_len - leftover_start;
4066         /* if buffer ends with something we couldn't parse,
4067            reparse it after appending the next read */
4068
4069     } else if (count == 0) {
4070         RemoveInputSource(isr);
4071         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4072     } else {
4073         DisplayFatalError(_("Error reading from ICS"), error, 1);
4074     }
4075 }
4076
4077
4078 /* Board style 12 looks like this:
4079
4080    <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
4081
4082  * The "<12> " is stripped before it gets to this routine.  The two
4083  * trailing 0's (flip state and clock ticking) are later addition, and
4084  * some chess servers may not have them, or may have only the first.
4085  * Additional trailing fields may be added in the future.
4086  */
4087
4088 #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"
4089
4090 #define RELATION_OBSERVING_PLAYED    0
4091 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4092 #define RELATION_PLAYING_MYMOVE      1
4093 #define RELATION_PLAYING_NOTMYMOVE  -1
4094 #define RELATION_EXAMINING           2
4095 #define RELATION_ISOLATED_BOARD     -3
4096 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4097
4098 void
4099 ParseBoard12(string)
4100      char *string;
4101 {
4102     GameMode newGameMode;
4103     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4104     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4105     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4106     char to_play, board_chars[200];
4107     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4108     char black[32], white[32];
4109     Board board;
4110     int prevMove = currentMove;
4111     int ticking = 2;
4112     ChessMove moveType;
4113     int fromX, fromY, toX, toY;
4114     char promoChar;
4115     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4116     char *bookHit = NULL; // [HGM] book
4117     Boolean weird = FALSE, reqFlag = FALSE;
4118
4119     fromX = fromY = toX = toY = -1;
4120
4121     newGame = FALSE;
4122
4123     if (appData.debugMode)
4124       fprintf(debugFP, _("Parsing board: %s\n"), string);
4125
4126     move_str[0] = NULLCHAR;
4127     elapsed_time[0] = NULLCHAR;
4128     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4129         int  i = 0, j;
4130         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4131             if(string[i] == ' ') { ranks++; files = 0; }
4132             else files++;
4133             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4134             i++;
4135         }
4136         for(j = 0; j <i; j++) board_chars[j] = string[j];
4137         board_chars[i] = '\0';
4138         string += i + 1;
4139     }
4140     n = sscanf(string, PATTERN, &to_play, &double_push,
4141                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4142                &gamenum, white, black, &relation, &basetime, &increment,
4143                &white_stren, &black_stren, &white_time, &black_time,
4144                &moveNum, str, elapsed_time, move_str, &ics_flip,
4145                &ticking);
4146
4147     if (n < 21) {
4148         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4149         DisplayError(str, 0);
4150         return;
4151     }
4152
4153     /* Convert the move number to internal form */
4154     moveNum = (moveNum - 1) * 2;
4155     if (to_play == 'B') moveNum++;
4156     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4157       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4158                         0, 1);
4159       return;
4160     }
4161
4162     switch (relation) {
4163       case RELATION_OBSERVING_PLAYED:
4164       case RELATION_OBSERVING_STATIC:
4165         if (gamenum == -1) {
4166             /* Old ICC buglet */
4167             relation = RELATION_OBSERVING_STATIC;
4168         }
4169         newGameMode = IcsObserving;
4170         break;
4171       case RELATION_PLAYING_MYMOVE:
4172       case RELATION_PLAYING_NOTMYMOVE:
4173         newGameMode =
4174           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4175             IcsPlayingWhite : IcsPlayingBlack;
4176         break;
4177       case RELATION_EXAMINING:
4178         newGameMode = IcsExamining;
4179         break;
4180       case RELATION_ISOLATED_BOARD:
4181       default:
4182         /* Just display this board.  If user was doing something else,
4183            we will forget about it until the next board comes. */
4184         newGameMode = IcsIdle;
4185         break;
4186       case RELATION_STARTING_POSITION:
4187         newGameMode = gameMode;
4188         break;
4189     }
4190
4191     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4192          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4193       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4194       char *toSqr;
4195       for (k = 0; k < ranks; k++) {
4196         for (j = 0; j < files; j++)
4197           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4198         if(gameInfo.holdingsWidth > 1) {
4199              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4200              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4201         }
4202       }
4203       CopyBoard(partnerBoard, board);
4204       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4205         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4206         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4207       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4208       if(toSqr = strchr(str, '-')) {
4209         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4210         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4211       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4212       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4213       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4214       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4215       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4216       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4217                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4218       DisplayMessage(partnerStatus, "");
4219         partnerBoardValid = TRUE;
4220       return;
4221     }
4222
4223     /* Modify behavior for initial board display on move listing
4224        of wild games.
4225        */
4226     switch (ics_getting_history) {
4227       case H_FALSE:
4228       case H_REQUESTED:
4229         break;
4230       case H_GOT_REQ_HEADER:
4231       case H_GOT_UNREQ_HEADER:
4232         /* This is the initial position of the current game */
4233         gamenum = ics_gamenum;
4234         moveNum = 0;            /* old ICS bug workaround */
4235         if (to_play == 'B') {
4236           startedFromSetupPosition = TRUE;
4237           blackPlaysFirst = TRUE;
4238           moveNum = 1;
4239           if (forwardMostMove == 0) forwardMostMove = 1;
4240           if (backwardMostMove == 0) backwardMostMove = 1;
4241           if (currentMove == 0) currentMove = 1;
4242         }
4243         newGameMode = gameMode;
4244         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4245         break;
4246       case H_GOT_UNWANTED_HEADER:
4247         /* This is an initial board that we don't want */
4248         return;
4249       case H_GETTING_MOVES:
4250         /* Should not happen */
4251         DisplayError(_("Error gathering move list: extra board"), 0);
4252         ics_getting_history = H_FALSE;
4253         return;
4254     }
4255
4256    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4257                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4258      /* [HGM] We seem to have switched variant unexpectedly
4259       * Try to guess new variant from board size
4260       */
4261           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4262           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4263           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4264           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4265           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4266           if(!weird) newVariant = VariantNormal;
4267           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4268           /* Get a move list just to see the header, which
4269              will tell us whether this is really bug or zh */
4270           if (ics_getting_history == H_FALSE) {
4271             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4272             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4273             SendToICS(str);
4274           }
4275     }
4276
4277     /* Take action if this is the first board of a new game, or of a
4278        different game than is currently being displayed.  */
4279     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4280         relation == RELATION_ISOLATED_BOARD) {
4281
4282         /* Forget the old game and get the history (if any) of the new one */
4283         if (gameMode != BeginningOfGame) {
4284           Reset(TRUE, TRUE);
4285         }
4286         newGame = TRUE;
4287         if (appData.autoRaiseBoard) BoardToTop();
4288         prevMove = -3;
4289         if (gamenum == -1) {
4290             newGameMode = IcsIdle;
4291         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4292                    appData.getMoveList && !reqFlag) {
4293             /* Need to get game history */
4294             ics_getting_history = H_REQUESTED;
4295             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4296             SendToICS(str);
4297         }
4298
4299         /* Initially flip the board to have black on the bottom if playing
4300            black or if the ICS flip flag is set, but let the user change
4301            it with the Flip View button. */
4302         flipView = appData.autoFlipView ?
4303           (newGameMode == IcsPlayingBlack) || ics_flip :
4304           appData.flipView;
4305
4306         /* Done with values from previous mode; copy in new ones */
4307         gameMode = newGameMode;
4308         ModeHighlight();
4309         ics_gamenum = gamenum;
4310         if (gamenum == gs_gamenum) {
4311             int klen = strlen(gs_kind);
4312             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4313             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4314             gameInfo.event = StrSave(str);
4315         } else {
4316             gameInfo.event = StrSave("ICS game");
4317         }
4318         gameInfo.site = StrSave(appData.icsHost);
4319         gameInfo.date = PGNDate();
4320         gameInfo.round = StrSave("-");
4321         gameInfo.white = StrSave(white);
4322         gameInfo.black = StrSave(black);
4323         timeControl = basetime * 60 * 1000;
4324         timeControl_2 = 0;
4325         timeIncrement = increment * 1000;
4326         movesPerSession = 0;
4327         gameInfo.timeControl = TimeControlTagValue();
4328         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4329   if (appData.debugMode) {
4330     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4331     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4332     setbuf(debugFP, NULL);
4333   }
4334
4335         gameInfo.outOfBook = NULL;
4336
4337         /* Do we have the ratings? */
4338         if (strcmp(player1Name, white) == 0 &&
4339             strcmp(player2Name, black) == 0) {
4340             if (appData.debugMode)
4341               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4342                       player1Rating, player2Rating);
4343             gameInfo.whiteRating = player1Rating;
4344             gameInfo.blackRating = player2Rating;
4345         } else if (strcmp(player2Name, white) == 0 &&
4346                    strcmp(player1Name, black) == 0) {
4347             if (appData.debugMode)
4348               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4349                       player2Rating, player1Rating);
4350             gameInfo.whiteRating = player2Rating;
4351             gameInfo.blackRating = player1Rating;
4352         }
4353         player1Name[0] = player2Name[0] = NULLCHAR;
4354
4355         /* Silence shouts if requested */
4356         if (appData.quietPlay &&
4357             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4358             SendToICS(ics_prefix);
4359             SendToICS("set shout 0\n");
4360         }
4361     }
4362
4363     /* Deal with midgame name changes */
4364     if (!newGame) {
4365         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4366             if (gameInfo.white) free(gameInfo.white);
4367             gameInfo.white = StrSave(white);
4368         }
4369         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4370             if (gameInfo.black) free(gameInfo.black);
4371             gameInfo.black = StrSave(black);
4372         }
4373     }
4374
4375     /* Throw away game result if anything actually changes in examine mode */
4376     if (gameMode == IcsExamining && !newGame) {
4377         gameInfo.result = GameUnfinished;
4378         if (gameInfo.resultDetails != NULL) {
4379             free(gameInfo.resultDetails);
4380             gameInfo.resultDetails = NULL;
4381         }
4382     }
4383
4384     /* In pausing && IcsExamining mode, we ignore boards coming
4385        in if they are in a different variation than we are. */
4386     if (pauseExamInvalid) return;
4387     if (pausing && gameMode == IcsExamining) {
4388         if (moveNum <= pauseExamForwardMostMove) {
4389             pauseExamInvalid = TRUE;
4390             forwardMostMove = pauseExamForwardMostMove;
4391             return;
4392         }
4393     }
4394
4395   if (appData.debugMode) {
4396     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4397   }
4398     /* Parse the board */
4399     for (k = 0; k < ranks; k++) {
4400       for (j = 0; j < files; j++)
4401         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4402       if(gameInfo.holdingsWidth > 1) {
4403            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4404            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4405       }
4406     }
4407     CopyBoard(boards[moveNum], board);
4408     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4409     if (moveNum == 0) {
4410         startedFromSetupPosition =
4411           !CompareBoards(board, initialPosition);
4412         if(startedFromSetupPosition)
4413             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4414     }
4415
4416     /* [HGM] Set castling rights. Take the outermost Rooks,
4417        to make it also work for FRC opening positions. Note that board12
4418        is really defective for later FRC positions, as it has no way to
4419        indicate which Rook can castle if they are on the same side of King.
4420        For the initial position we grant rights to the outermost Rooks,
4421        and remember thos rights, and we then copy them on positions
4422        later in an FRC game. This means WB might not recognize castlings with
4423        Rooks that have moved back to their original position as illegal,
4424        but in ICS mode that is not its job anyway.
4425     */
4426     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4427     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4428
4429         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4430             if(board[0][i] == WhiteRook) j = i;
4431         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4432         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4433             if(board[0][i] == WhiteRook) j = i;
4434         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4435         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4436             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4437         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4438         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4439             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4440         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4441
4442         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4443         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4444             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4445         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4446             if(board[BOARD_HEIGHT-1][k] == bKing)
4447                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4448         if(gameInfo.variant == VariantTwoKings) {
4449             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4450             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4451             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4452         }
4453     } else { int r;
4454         r = boards[moveNum][CASTLING][0] = initialRights[0];
4455         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4456         r = boards[moveNum][CASTLING][1] = initialRights[1];
4457         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4458         r = boards[moveNum][CASTLING][3] = initialRights[3];
4459         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4460         r = boards[moveNum][CASTLING][4] = initialRights[4];
4461         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4462         /* wildcastle kludge: always assume King has rights */
4463         r = boards[moveNum][CASTLING][2] = initialRights[2];
4464         r = boards[moveNum][CASTLING][5] = initialRights[5];
4465     }
4466     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4467     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4468
4469
4470     if (ics_getting_history == H_GOT_REQ_HEADER ||
4471         ics_getting_history == H_GOT_UNREQ_HEADER) {
4472         /* This was an initial position from a move list, not
4473            the current position */
4474         return;
4475     }
4476
4477     /* Update currentMove and known move number limits */
4478     newMove = newGame || moveNum > forwardMostMove;
4479
4480     if (newGame) {
4481         forwardMostMove = backwardMostMove = currentMove = moveNum;
4482         if (gameMode == IcsExamining && moveNum == 0) {
4483           /* Workaround for ICS limitation: we are not told the wild
4484              type when starting to examine a game.  But if we ask for
4485              the move list, the move list header will tell us */
4486             ics_getting_history = H_REQUESTED;
4487             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4488             SendToICS(str);
4489         }
4490     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4491                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4492 #if ZIPPY
4493         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4494         /* [HGM] applied this also to an engine that is silently watching        */
4495         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4496             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4497             gameInfo.variant == currentlyInitializedVariant) {
4498           takeback = forwardMostMove - moveNum;
4499           for (i = 0; i < takeback; i++) {
4500             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4501             SendToProgram("undo\n", &first);
4502           }
4503         }
4504 #endif
4505
4506         forwardMostMove = moveNum;
4507         if (!pausing || currentMove > forwardMostMove)
4508           currentMove = forwardMostMove;
4509     } else {
4510         /* New part of history that is not contiguous with old part */
4511         if (pausing && gameMode == IcsExamining) {
4512             pauseExamInvalid = TRUE;
4513             forwardMostMove = pauseExamForwardMostMove;
4514             return;
4515         }
4516         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4517 #if ZIPPY
4518             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4519                 // [HGM] when we will receive the move list we now request, it will be
4520                 // fed to the engine from the first move on. So if the engine is not
4521                 // in the initial position now, bring it there.
4522                 InitChessProgram(&first, 0);
4523             }
4524 #endif
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529         forwardMostMove = backwardMostMove = currentMove = moveNum;
4530     }
4531
4532     /* Update the clocks */
4533     if (strchr(elapsed_time, '.')) {
4534       /* Time is in ms */
4535       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4536       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4537     } else {
4538       /* Time is in seconds */
4539       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4540       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4541     }
4542
4543
4544 #if ZIPPY
4545     if (appData.zippyPlay && newGame &&
4546         gameMode != IcsObserving && gameMode != IcsIdle &&
4547         gameMode != IcsExamining)
4548       ZippyFirstBoard(moveNum, basetime, increment);
4549 #endif
4550
4551     /* Put the move on the move list, first converting
4552        to canonical algebraic form. */
4553     if (moveNum > 0) {
4554   if (appData.debugMode) {
4555     if (appData.debugMode) { int f = forwardMostMove;
4556         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4557                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4558                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4559     }
4560     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4561     fprintf(debugFP, "moveNum = %d\n", moveNum);
4562     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4563     setbuf(debugFP, NULL);
4564   }
4565         if (moveNum <= backwardMostMove) {
4566             /* We don't know what the board looked like before
4567                this move.  Punt. */
4568           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4569             strcat(parseList[moveNum - 1], " ");
4570             strcat(parseList[moveNum - 1], elapsed_time);
4571             moveList[moveNum - 1][0] = NULLCHAR;
4572         } else if (strcmp(move_str, "none") == 0) {
4573             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4574             /* Again, we don't know what the board looked like;
4575                this is really the start of the game. */
4576             parseList[moveNum - 1][0] = NULLCHAR;
4577             moveList[moveNum - 1][0] = NULLCHAR;
4578             backwardMostMove = moveNum;
4579             startedFromSetupPosition = TRUE;
4580             fromX = fromY = toX = toY = -1;
4581         } else {
4582           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4583           //                 So we parse the long-algebraic move string in stead of the SAN move
4584           int valid; char buf[MSG_SIZ], *prom;
4585
4586           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4587                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4588           // str looks something like "Q/a1-a2"; kill the slash
4589           if(str[1] == '/')
4590             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4591           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4592           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4593                 strcat(buf, prom); // long move lacks promo specification!
4594           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4595                 if(appData.debugMode)
4596                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4597                 safeStrCpy(move_str, buf, MSG_SIZ);
4598           }
4599           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4600                                 &fromX, &fromY, &toX, &toY, &promoChar)
4601                || ParseOneMove(buf, moveNum - 1, &moveType,
4602                                 &fromX, &fromY, &toX, &toY, &promoChar);
4603           // end of long SAN patch
4604           if (valid) {
4605             (void) CoordsToAlgebraic(boards[moveNum - 1],
4606                                      PosFlags(moveNum - 1),
4607                                      fromY, fromX, toY, toX, promoChar,
4608                                      parseList[moveNum-1]);
4609             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4610               case MT_NONE:
4611               case MT_STALEMATE:
4612               default:
4613                 break;
4614               case MT_CHECK:
4615                 if(gameInfo.variant != VariantShogi)
4616                     strcat(parseList[moveNum - 1], "+");
4617                 break;
4618               case MT_CHECKMATE:
4619               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4620                 strcat(parseList[moveNum - 1], "#");
4621                 break;
4622             }
4623             strcat(parseList[moveNum - 1], " ");
4624             strcat(parseList[moveNum - 1], elapsed_time);
4625             /* currentMoveString is set as a side-effect of ParseOneMove */
4626             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4627             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4628             strcat(moveList[moveNum - 1], "\n");
4629
4630             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4631                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4632               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4633                 ChessSquare old, new = boards[moveNum][k][j];
4634                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4635                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4636                   if(old == new) continue;
4637                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4638                   else if(new == WhiteWazir || new == BlackWazir) {
4639                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4640                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4641                       else boards[moveNum][k][j] = old; // preserve type of Gold
4642                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4643                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4644               }
4645           } else {
4646             /* Move from ICS was illegal!?  Punt. */
4647             if (appData.debugMode) {
4648               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4649               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4650             }
4651             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4652             strcat(parseList[moveNum - 1], " ");
4653             strcat(parseList[moveNum - 1], elapsed_time);
4654             moveList[moveNum - 1][0] = NULLCHAR;
4655             fromX = fromY = toX = toY = -1;
4656           }
4657         }
4658   if (appData.debugMode) {
4659     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4660     setbuf(debugFP, NULL);
4661   }
4662
4663 #if ZIPPY
4664         /* Send move to chess program (BEFORE animating it). */
4665         if (appData.zippyPlay && !newGame && newMove &&
4666            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4667
4668             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4669                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4670                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4671                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4672                             move_str);
4673                     DisplayError(str, 0);
4674                 } else {
4675                     if (first.sendTime) {
4676                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4677                     }
4678                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4679                     if (firstMove && !bookHit) {
4680                         firstMove = FALSE;
4681                         if (first.useColors) {
4682                           SendToProgram(gameMode == IcsPlayingWhite ?
4683                                         "white\ngo\n" :
4684                                         "black\ngo\n", &first);
4685                         } else {
4686                           SendToProgram("go\n", &first);
4687                         }
4688                         first.maybeThinking = TRUE;
4689                     }
4690                 }
4691             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4692               if (moveList[moveNum - 1][0] == NULLCHAR) {
4693                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4694                 DisplayError(str, 0);
4695               } else {
4696                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4697                 SendMoveToProgram(moveNum - 1, &first);
4698               }
4699             }
4700         }
4701 #endif
4702     }
4703
4704     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4705         /* If move comes from a remote source, animate it.  If it
4706            isn't remote, it will have already been animated. */
4707         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4708             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4709         }
4710         if (!pausing && appData.highlightLastMove) {
4711             SetHighlights(fromX, fromY, toX, toY);
4712         }
4713     }
4714
4715     /* Start the clocks */
4716     whiteFlag = blackFlag = FALSE;
4717     appData.clockMode = !(basetime == 0 && increment == 0);
4718     if (ticking == 0) {
4719       ics_clock_paused = TRUE;
4720       StopClocks();
4721     } else if (ticking == 1) {
4722       ics_clock_paused = FALSE;
4723     }
4724     if (gameMode == IcsIdle ||
4725         relation == RELATION_OBSERVING_STATIC ||
4726         relation == RELATION_EXAMINING ||
4727         ics_clock_paused)
4728       DisplayBothClocks();
4729     else
4730       StartClocks();
4731
4732     /* Display opponents and material strengths */
4733     if (gameInfo.variant != VariantBughouse &&
4734         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4735         if (tinyLayout || smallLayout) {
4736             if(gameInfo.variant == VariantNormal)
4737               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4738                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4739                     basetime, increment);
4740             else
4741               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4742                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4743                     basetime, increment, (int) gameInfo.variant);
4744         } else {
4745             if(gameInfo.variant == VariantNormal)
4746               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4747                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4748                     basetime, increment);
4749             else
4750               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4751                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4752                     basetime, increment, VariantName(gameInfo.variant));
4753         }
4754         DisplayTitle(str);
4755   if (appData.debugMode) {
4756     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4757   }
4758     }
4759
4760
4761     /* Display the board */
4762     if (!pausing && !appData.noGUI) {
4763
4764       if (appData.premove)
4765           if (!gotPremove ||
4766              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4767              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4768               ClearPremoveHighlights();
4769
4770       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4771         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4772       DrawPosition(j, boards[currentMove]);
4773
4774       DisplayMove(moveNum - 1);
4775       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4776             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4777               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4778         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4779       }
4780     }
4781
4782     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4783 #if ZIPPY
4784     if(bookHit) { // [HGM] book: simulate book reply
4785         static char bookMove[MSG_SIZ]; // a bit generous?
4786
4787         programStats.nodes = programStats.depth = programStats.time =
4788         programStats.score = programStats.got_only_move = 0;
4789         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4790
4791         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4792         strcat(bookMove, bookHit);
4793         HandleMachineMove(bookMove, &first);
4794     }
4795 #endif
4796 }
4797
4798 void
4799 GetMoveListEvent()
4800 {
4801     char buf[MSG_SIZ];
4802     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4803         ics_getting_history = H_REQUESTED;
4804         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4805         SendToICS(buf);
4806     }
4807 }
4808
4809 void
4810 AnalysisPeriodicEvent(force)
4811      int force;
4812 {
4813     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4814          && !force) || !appData.periodicUpdates)
4815       return;
4816
4817     /* Send . command to Crafty to collect stats */
4818     SendToProgram(".\n", &first);
4819
4820     /* Don't send another until we get a response (this makes
4821        us stop sending to old Crafty's which don't understand
4822        the "." command (sending illegal cmds resets node count & time,
4823        which looks bad)) */
4824     programStats.ok_to_send = 0;
4825 }
4826
4827 void ics_update_width(new_width)
4828         int new_width;
4829 {
4830         ics_printf("set width %d\n", new_width);
4831 }
4832
4833 void
4834 SendMoveToProgram(moveNum, cps)
4835      int moveNum;
4836      ChessProgramState *cps;
4837 {
4838     char buf[MSG_SIZ];
4839
4840     if (cps->useUsermove) {
4841       SendToProgram("usermove ", cps);
4842     }
4843     if (cps->useSAN) {
4844       char *space;
4845       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4846         int len = space - parseList[moveNum];
4847         memcpy(buf, parseList[moveNum], len);
4848         buf[len++] = '\n';
4849         buf[len] = NULLCHAR;
4850       } else {
4851         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4852       }
4853       SendToProgram(buf, cps);
4854     } else {
4855       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4856         AlphaRank(moveList[moveNum], 4);
4857         SendToProgram(moveList[moveNum], cps);
4858         AlphaRank(moveList[moveNum], 4); // and back
4859       } else
4860       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4861        * the engine. It would be nice to have a better way to identify castle
4862        * moves here. */
4863       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4864                                                                          && cps->useOOCastle) {
4865         int fromX = moveList[moveNum][0] - AAA;
4866         int fromY = moveList[moveNum][1] - ONE;
4867         int toX = moveList[moveNum][2] - AAA;
4868         int toY = moveList[moveNum][3] - ONE;
4869         if((boards[moveNum][fromY][fromX] == WhiteKing
4870             && boards[moveNum][toY][toX] == WhiteRook)
4871            || (boards[moveNum][fromY][fromX] == BlackKing
4872                && boards[moveNum][toY][toX] == BlackRook)) {
4873           if(toX > fromX) SendToProgram("O-O\n", cps);
4874           else SendToProgram("O-O-O\n", cps);
4875         }
4876         else SendToProgram(moveList[moveNum], cps);
4877       }
4878       else SendToProgram(moveList[moveNum], cps);
4879       /* End of additions by Tord */
4880     }
4881
4882     /* [HGM] setting up the opening has brought engine in force mode! */
4883     /*       Send 'go' if we are in a mode where machine should play. */
4884     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4885         (gameMode == TwoMachinesPlay   ||
4886 #if ZIPPY
4887          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4888 #endif
4889          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4890         SendToProgram("go\n", cps);
4891   if (appData.debugMode) {
4892     fprintf(debugFP, "(extra)\n");
4893   }
4894     }
4895     setboardSpoiledMachineBlack = 0;
4896 }
4897
4898 void
4899 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4900      ChessMove moveType;
4901      int fromX, fromY, toX, toY;
4902      char promoChar;
4903 {
4904     char user_move[MSG_SIZ];
4905
4906     switch (moveType) {
4907       default:
4908         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4909                 (int)moveType, fromX, fromY, toX, toY);
4910         DisplayError(user_move + strlen("say "), 0);
4911         break;
4912       case WhiteKingSideCastle:
4913       case BlackKingSideCastle:
4914       case WhiteQueenSideCastleWild:
4915       case BlackQueenSideCastleWild:
4916       /* PUSH Fabien */
4917       case WhiteHSideCastleFR:
4918       case BlackHSideCastleFR:
4919       /* POP Fabien */
4920         snprintf(user_move, MSG_SIZ, "o-o\n");
4921         break;
4922       case WhiteQueenSideCastle:
4923       case BlackQueenSideCastle:
4924       case WhiteKingSideCastleWild:
4925       case BlackKingSideCastleWild:
4926       /* PUSH Fabien */
4927       case WhiteASideCastleFR:
4928       case BlackASideCastleFR:
4929       /* POP Fabien */
4930         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4931         break;
4932       case WhiteNonPromotion:
4933       case BlackNonPromotion:
4934         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4935         break;
4936       case WhitePromotion:
4937       case BlackPromotion:
4938         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4939           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4940                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4941                 PieceToChar(WhiteFerz));
4942         else if(gameInfo.variant == VariantGreat)
4943           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4944                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4945                 PieceToChar(WhiteMan));
4946         else
4947           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4948                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4949                 promoChar);
4950         break;
4951       case WhiteDrop:
4952       case BlackDrop:
4953       drop:
4954         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4955                  ToUpper(PieceToChar((ChessSquare) fromX)),
4956                  AAA + toX, ONE + toY);
4957         break;
4958       case IllegalMove:  /* could be a variant we don't quite understand */
4959         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4960       case NormalMove:
4961       case WhiteCapturesEnPassant:
4962       case BlackCapturesEnPassant:
4963         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4964                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4965         break;
4966     }
4967     SendToICS(user_move);
4968     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4969         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4970 }
4971
4972 void
4973 UploadGameEvent()
4974 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4975     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4976     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4977     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4978         DisplayError("You cannot do this while you are playing or observing", 0);
4979         return;
4980     }
4981     if(gameMode != IcsExamining) { // is this ever not the case?
4982         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4983
4984         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4985           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4986         } else { // on FICS we must first go to general examine mode
4987           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4988         }
4989         if(gameInfo.variant != VariantNormal) {
4990             // try figure out wild number, as xboard names are not always valid on ICS
4991             for(i=1; i<=36; i++) {
4992               snprintf(buf, MSG_SIZ, "wild/%d", i);
4993                 if(StringToVariant(buf) == gameInfo.variant) break;
4994             }
4995             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4996             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4997             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4998         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4999         SendToICS(ics_prefix);
5000         SendToICS(buf);
5001         if(startedFromSetupPosition || backwardMostMove != 0) {
5002           fen = PositionToFEN(backwardMostMove, NULL);
5003           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5004             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5005             SendToICS(buf);
5006           } else { // FICS: everything has to set by separate bsetup commands
5007             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5008             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5009             SendToICS(buf);
5010             if(!WhiteOnMove(backwardMostMove)) {
5011                 SendToICS("bsetup tomove black\n");
5012             }
5013             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5014             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5015             SendToICS(buf);
5016             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5017             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5018             SendToICS(buf);
5019             i = boards[backwardMostMove][EP_STATUS];
5020             if(i >= 0) { // set e.p.
5021               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5022                 SendToICS(buf);
5023             }
5024             bsetup++;
5025           }
5026         }
5027       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5028             SendToICS("bsetup done\n"); // switch to normal examining.
5029     }
5030     for(i = backwardMostMove; i<last; i++) {
5031         char buf[20];
5032         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5033         SendToICS(buf);
5034     }
5035     SendToICS(ics_prefix);
5036     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5037 }
5038
5039 void
5040 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5041      int rf, ff, rt, ft;
5042      char promoChar;
5043      char move[7];
5044 {
5045     if (rf == DROP_RANK) {
5046       sprintf(move, "%c@%c%c\n",
5047                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5048     } else {
5049         if (promoChar == 'x' || promoChar == NULLCHAR) {
5050           sprintf(move, "%c%c%c%c\n",
5051                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5052         } else {
5053             sprintf(move, "%c%c%c%c%c\n",
5054                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5055         }
5056     }
5057 }
5058
5059 void
5060 ProcessICSInitScript(f)
5061      FILE *f;
5062 {
5063     char buf[MSG_SIZ];
5064
5065     while (fgets(buf, MSG_SIZ, f)) {
5066         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5067     }
5068
5069     fclose(f);
5070 }
5071
5072
5073 static int lastX, lastY, selectFlag, dragging;
5074
5075 void
5076 Sweep(int step)
5077 {
5078     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5079     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5080     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5081     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5082     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5083     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5084     do {
5085         promoSweep -= step;
5086         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5087         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5088         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5089         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5090         if(!step) step = 1;
5091     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5092             appData.testLegality && (promoSweep == king ||
5093             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5094     ChangeDragPiece(promoSweep);
5095 }
5096
5097 int PromoScroll(int x, int y)
5098 {
5099   int step = 0;
5100
5101   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5102   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5103   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5104   if(!step) return FALSE;
5105   lastX = x; lastY = y;
5106   if((promoSweep < BlackPawn) == flipView) step = -step;
5107   if(step > 0) selectFlag = 1;
5108   if(!selectFlag) Sweep(step);
5109   return FALSE;
5110 }
5111
5112 void
5113 NextPiece(int step)
5114 {
5115     ChessSquare piece = boards[currentMove][toY][toX];
5116     do {
5117         pieceSweep -= step;
5118         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5119         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5120         if(!step) step = -1;
5121     } while(PieceToChar(pieceSweep) == '.');
5122     boards[currentMove][toY][toX] = pieceSweep;
5123     DrawPosition(FALSE, boards[currentMove]);
5124     boards[currentMove][toY][toX] = piece;
5125 }
5126 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5127 void
5128 AlphaRank(char *move, int n)
5129 {
5130 //    char *p = move, c; int x, y;
5131
5132     if (appData.debugMode) {
5133         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5134     }
5135
5136     if(move[1]=='*' &&
5137        move[2]>='0' && move[2]<='9' &&
5138        move[3]>='a' && move[3]<='x'    ) {
5139         move[1] = '@';
5140         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5141         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5142     } else
5143     if(move[0]>='0' && move[0]<='9' &&
5144        move[1]>='a' && move[1]<='x' &&
5145        move[2]>='0' && move[2]<='9' &&
5146        move[3]>='a' && move[3]<='x'    ) {
5147         /* input move, Shogi -> normal */
5148         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5149         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
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[1]=='@' &&
5154        move[3]>='0' && move[3]<='9' &&
5155        move[2]>='a' && move[2]<='x'    ) {
5156         move[1] = '*';
5157         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5158         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5159     } else
5160     if(
5161        move[0]>='a' && move[0]<='x' &&
5162        move[3]>='0' && move[3]<='9' &&
5163        move[2]>='a' && move[2]<='x'    ) {
5164          /* output move, normal -> Shogi */
5165         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5166         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5167         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5168         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5169         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5170     }
5171     if (appData.debugMode) {
5172         fprintf(debugFP, "   out = '%s'\n", move);
5173     }
5174 }
5175
5176 char yy_textstr[8000];
5177
5178 /* Parser for moves from gnuchess, ICS, or user typein box */
5179 Boolean
5180 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5181      char *move;
5182      int moveNum;
5183      ChessMove *moveType;
5184      int *fromX, *fromY, *toX, *toY;
5185      char *promoChar;
5186 {
5187     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5188
5189     switch (*moveType) {
5190       case WhitePromotion:
5191       case BlackPromotion:
5192       case WhiteNonPromotion:
5193       case BlackNonPromotion:
5194       case NormalMove:
5195       case WhiteCapturesEnPassant:
5196       case BlackCapturesEnPassant:
5197       case WhiteKingSideCastle:
5198       case WhiteQueenSideCastle:
5199       case BlackKingSideCastle:
5200       case BlackQueenSideCastle:
5201       case WhiteKingSideCastleWild:
5202       case WhiteQueenSideCastleWild:
5203       case BlackKingSideCastleWild:
5204       case BlackQueenSideCastleWild:
5205       /* Code added by Tord: */
5206       case WhiteHSideCastleFR:
5207       case WhiteASideCastleFR:
5208       case BlackHSideCastleFR:
5209       case BlackASideCastleFR:
5210       /* End of code added by Tord */
5211       case IllegalMove:         /* bug or odd chess variant */
5212         *fromX = currentMoveString[0] - AAA;
5213         *fromY = currentMoveString[1] - ONE;
5214         *toX = currentMoveString[2] - AAA;
5215         *toY = currentMoveString[3] - ONE;
5216         *promoChar = currentMoveString[4];
5217         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5218             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5219     if (appData.debugMode) {
5220         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5221     }
5222             *fromX = *fromY = *toX = *toY = 0;
5223             return FALSE;
5224         }
5225         if (appData.testLegality) {
5226           return (*moveType != IllegalMove);
5227         } else {
5228           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5229                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5230         }
5231
5232       case WhiteDrop:
5233       case BlackDrop:
5234         *fromX = *moveType == WhiteDrop ?
5235           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5236           (int) CharToPiece(ToLower(currentMoveString[0]));
5237         *fromY = DROP_RANK;
5238         *toX = currentMoveString[2] - AAA;
5239         *toY = currentMoveString[3] - ONE;
5240         *promoChar = NULLCHAR;
5241         return TRUE;
5242
5243       case AmbiguousMove:
5244       case ImpossibleMove:
5245       case EndOfFile:
5246       case ElapsedTime:
5247       case Comment:
5248       case PGNTag:
5249       case NAG:
5250       case WhiteWins:
5251       case BlackWins:
5252       case GameIsDrawn:
5253       default:
5254     if (appData.debugMode) {
5255         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5256     }
5257         /* bug? */
5258         *fromX = *fromY = *toX = *toY = 0;
5259         *promoChar = NULLCHAR;
5260         return FALSE;
5261     }
5262 }
5263
5264 Boolean pushed = FALSE;
5265
5266 void
5267 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5268 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5269   int fromX, fromY, toX, toY; char promoChar;
5270   ChessMove moveType;
5271   Boolean valid;
5272   int nr = 0;
5273
5274   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5275     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5276     pushed = TRUE;
5277   }
5278   endPV = forwardMostMove;
5279   do {
5280     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5281     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5282     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5283 if(appData.debugMode){
5284 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);
5285 }
5286     if(!valid && nr == 0 &&
5287        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5288         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5289         // Hande case where played move is different from leading PV move
5290         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5291         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5292         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5293         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5294           endPV += 2; // if position different, keep this
5295           moveList[endPV-1][0] = fromX + AAA;
5296           moveList[endPV-1][1] = fromY + ONE;
5297           moveList[endPV-1][2] = toX + AAA;
5298           moveList[endPV-1][3] = toY + ONE;
5299           parseList[endPV-1][0] = NULLCHAR;
5300           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5301         }
5302       }
5303     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5304     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5305     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5306     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5307         valid++; // allow comments in PV
5308         continue;
5309     }
5310     nr++;
5311     if(endPV+1 > framePtr) break; // no space, truncate
5312     if(!valid) break;
5313     endPV++;
5314     CopyBoard(boards[endPV], boards[endPV-1]);
5315     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5316     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5317     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5318     CoordsToAlgebraic(boards[endPV - 1],
5319                              PosFlags(endPV - 1),
5320                              fromY, fromX, toY, toX, promoChar,
5321                              parseList[endPV - 1]);
5322   } while(valid);
5323   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5324   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5325   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5326                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5327   DrawPosition(TRUE, boards[currentMove]);
5328 }
5329
5330 int
5331 MultiPV(ChessProgramState *cps)
5332 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5333         int i;
5334         for(i=0; i<cps->nrOptions; i++)
5335             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5336                 return i;
5337         return -1;
5338 }
5339
5340 Boolean
5341 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5342 {
5343         int startPV, multi, lineStart, origIndex = index;
5344         char *p, buf2[MSG_SIZ];
5345
5346         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5347         lastX = x; lastY = y;
5348         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5349         lineStart = startPV = index;
5350         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5351         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5352         index = startPV;
5353         do{ while(buf[index] && buf[index] != '\n') index++;
5354         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5355         buf[index] = 0;
5356         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5357                 int n = first.option[multi].value;
5358                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5359                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5360                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5361                 first.option[multi].value = n;
5362                 *start = *end = 0;
5363                 return FALSE;
5364         }
5365         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5366         *start = startPV; *end = index-1;
5367         return TRUE;
5368 }
5369
5370 Boolean
5371 LoadPV(int x, int y)
5372 { // called on right mouse click to load PV
5373   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5374   lastX = x; lastY = y;
5375   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5376   return TRUE;
5377 }
5378
5379 void
5380 UnLoadPV()
5381 {
5382   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5383   if(endPV < 0) return;
5384   endPV = -1;
5385   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5386         Boolean saveAnimate = appData.animate;
5387         if(pushed) {
5388             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5389                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5390             } else storedGames--; // abandon shelved tail of original game
5391         }
5392         pushed = FALSE;
5393         forwardMostMove = currentMove;
5394         currentMove = oldFMM;
5395         appData.animate = FALSE;
5396         ToNrEvent(forwardMostMove);
5397         appData.animate = saveAnimate;
5398   }
5399   currentMove = forwardMostMove;
5400   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5401   ClearPremoveHighlights();
5402   DrawPosition(TRUE, boards[currentMove]);
5403 }
5404
5405 void
5406 MovePV(int x, int y, int h)
5407 { // step through PV based on mouse coordinates (called on mouse move)
5408   int margin = h>>3, step = 0;
5409
5410   // we must somehow check if right button is still down (might be released off board!)
5411   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5412   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5413   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5414   if(!step) return;
5415   lastX = x; lastY = y;
5416
5417   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5418   if(endPV < 0) return;
5419   if(y < margin) step = 1; else
5420   if(y > h - margin) step = -1;
5421   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5422   currentMove += step;
5423   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5424   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5425                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5426   DrawPosition(FALSE, boards[currentMove]);
5427 }
5428
5429
5430 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5431 // All positions will have equal probability, but the current method will not provide a unique
5432 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5433 #define DARK 1
5434 #define LITE 2
5435 #define ANY 3
5436
5437 int squaresLeft[4];
5438 int piecesLeft[(int)BlackPawn];
5439 int seed, nrOfShuffles;
5440
5441 void GetPositionNumber()
5442 {       // sets global variable seed
5443         int i;
5444
5445         seed = appData.defaultFrcPosition;
5446         if(seed < 0) { // randomize based on time for negative FRC position numbers
5447                 for(i=0; i<50; i++) seed += random();
5448                 seed = random() ^ random() >> 8 ^ random() << 8;
5449                 if(seed<0) seed = -seed;
5450         }
5451 }
5452
5453 int put(Board board, int pieceType, int rank, int n, int shade)
5454 // put the piece on the (n-1)-th empty squares of the given shade
5455 {
5456         int i;
5457
5458         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5459                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5460                         board[rank][i] = (ChessSquare) pieceType;
5461                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5462                         squaresLeft[ANY]--;
5463                         piecesLeft[pieceType]--;
5464                         return i;
5465                 }
5466         }
5467         return -1;
5468 }
5469
5470
5471 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5472 // calculate where the next piece goes, (any empty square), and put it there
5473 {
5474         int i;
5475
5476         i = seed % squaresLeft[shade];
5477         nrOfShuffles *= squaresLeft[shade];
5478         seed /= squaresLeft[shade];
5479         put(board, pieceType, rank, i, shade);
5480 }
5481
5482 void AddTwoPieces(Board board, int pieceType, int rank)
5483 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5484 {
5485         int i, n=squaresLeft[ANY], j=n-1, k;
5486
5487         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5488         i = seed % k;  // pick one
5489         nrOfShuffles *= k;
5490         seed /= k;
5491         while(i >= j) i -= j--;
5492         j = n - 1 - j; i += j;
5493         put(board, pieceType, rank, j, ANY);
5494         put(board, pieceType, rank, i, ANY);
5495 }
5496
5497 void SetUpShuffle(Board board, int number)
5498 {
5499         int i, p, first=1;
5500
5501         GetPositionNumber(); nrOfShuffles = 1;
5502
5503         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5504         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5505         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5506
5507         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5508
5509         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5510             p = (int) board[0][i];
5511             if(p < (int) BlackPawn) piecesLeft[p] ++;
5512             board[0][i] = EmptySquare;
5513         }
5514
5515         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5516             // shuffles restricted to allow normal castling put KRR first
5517             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5518                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5519             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5520                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5521             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5522                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5523             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5524                 put(board, WhiteRook, 0, 0, ANY);
5525             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5526         }
5527
5528         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5529             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5530             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5531                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5532                 while(piecesLeft[p] >= 2) {
5533                     AddOnePiece(board, p, 0, LITE);
5534                     AddOnePiece(board, p, 0, DARK);
5535                 }
5536                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5537             }
5538
5539         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5540             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5541             // but we leave King and Rooks for last, to possibly obey FRC restriction
5542             if(p == (int)WhiteRook) continue;
5543             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5544             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5545         }
5546
5547         // now everything is placed, except perhaps King (Unicorn) and Rooks
5548
5549         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5550             // Last King gets castling rights
5551             while(piecesLeft[(int)WhiteUnicorn]) {
5552                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5553                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5554             }
5555
5556             while(piecesLeft[(int)WhiteKing]) {
5557                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5558                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5559             }
5560
5561
5562         } else {
5563             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5564             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5565         }
5566
5567         // Only Rooks can be left; simply place them all
5568         while(piecesLeft[(int)WhiteRook]) {
5569                 i = put(board, WhiteRook, 0, 0, ANY);
5570                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5571                         if(first) {
5572                                 first=0;
5573                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5574                         }
5575                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5576                 }
5577         }
5578         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5579             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5580         }
5581
5582         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5583 }
5584
5585 int SetCharTable( char *table, const char * map )
5586 /* [HGM] moved here from winboard.c because of its general usefulness */
5587 /*       Basically a safe strcpy that uses the last character as King */
5588 {
5589     int result = FALSE; int NrPieces;
5590
5591     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5592                     && NrPieces >= 12 && !(NrPieces&1)) {
5593         int i; /* [HGM] Accept even length from 12 to 34 */
5594
5595         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5596         for( i=0; i<NrPieces/2-1; i++ ) {
5597             table[i] = map[i];
5598             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5599         }
5600         table[(int) WhiteKing]  = map[NrPieces/2-1];
5601         table[(int) BlackKing]  = map[NrPieces-1];
5602
5603         result = TRUE;
5604     }
5605
5606     return result;
5607 }
5608
5609 void Prelude(Board board)
5610 {       // [HGM] superchess: random selection of exo-pieces
5611         int i, j, k; ChessSquare p;
5612         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5613
5614         GetPositionNumber(); // use FRC position number
5615
5616         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5617             SetCharTable(pieceToChar, appData.pieceToCharTable);
5618             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5619                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5620         }
5621
5622         j = seed%4;                 seed /= 4;
5623         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5624         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5625         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5626         j = seed%3 + (seed%3 >= j); seed /= 3;
5627         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5628         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5629         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5630         j = seed%3;                 seed /= 3;
5631         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5632         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5633         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5634         j = seed%2 + (seed%2 >= j); seed /= 2;
5635         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5636         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5637         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5638         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5639         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5640         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5641         put(board, exoPieces[0],    0, 0, ANY);
5642         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5643 }
5644
5645 void
5646 InitPosition(redraw)
5647      int redraw;
5648 {
5649     ChessSquare (* pieces)[BOARD_FILES];
5650     int i, j, pawnRow, overrule,
5651     oldx = gameInfo.boardWidth,
5652     oldy = gameInfo.boardHeight,
5653     oldh = gameInfo.holdingsWidth;
5654     static int oldv;
5655
5656     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5657
5658     /* [AS] Initialize pv info list [HGM] and game status */
5659     {
5660         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5661             pvInfoList[i].depth = 0;
5662             boards[i][EP_STATUS] = EP_NONE;
5663             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5664         }
5665
5666         initialRulePlies = 0; /* 50-move counter start */
5667
5668         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5669         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5670     }
5671
5672
5673     /* [HGM] logic here is completely changed. In stead of full positions */
5674     /* the initialized data only consist of the two backranks. The switch */
5675     /* selects which one we will use, which is than copied to the Board   */
5676     /* initialPosition, which for the rest is initialized by Pawns and    */
5677     /* empty squares. This initial position is then copied to boards[0],  */
5678     /* possibly after shuffling, so that it remains available.            */
5679
5680     gameInfo.holdingsWidth = 0; /* default board sizes */
5681     gameInfo.boardWidth    = 8;
5682     gameInfo.boardHeight   = 8;
5683     gameInfo.holdingsSize  = 0;
5684     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5685     for(i=0; i<BOARD_FILES-2; i++)
5686       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5687     initialPosition[EP_STATUS] = EP_NONE;
5688     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5689     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5690          SetCharTable(pieceNickName, appData.pieceNickNames);
5691     else SetCharTable(pieceNickName, "............");
5692     pieces = FIDEArray;
5693
5694     switch (gameInfo.variant) {
5695     case VariantFischeRandom:
5696       shuffleOpenings = TRUE;
5697     default:
5698       break;
5699     case VariantShatranj:
5700       pieces = ShatranjArray;
5701       nrCastlingRights = 0;
5702       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5703       break;
5704     case VariantMakruk:
5705       pieces = makrukArray;
5706       nrCastlingRights = 0;
5707       startedFromSetupPosition = TRUE;
5708       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5709       break;
5710     case VariantTwoKings:
5711       pieces = twoKingsArray;
5712       break;
5713     case VariantCapaRandom:
5714       shuffleOpenings = TRUE;
5715     case VariantCapablanca:
5716       pieces = CapablancaArray;
5717       gameInfo.boardWidth = 10;
5718       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5719       break;
5720     case VariantGothic:
5721       pieces = GothicArray;
5722       gameInfo.boardWidth = 10;
5723       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5724       break;
5725     case VariantSChess:
5726       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5727       gameInfo.holdingsSize = 7;
5728       break;
5729     case VariantJanus:
5730       pieces = JanusArray;
5731       gameInfo.boardWidth = 10;
5732       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5733       nrCastlingRights = 6;
5734         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5735         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5736         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5737         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5738         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5739         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5740       break;
5741     case VariantFalcon:
5742       pieces = FalconArray;
5743       gameInfo.boardWidth = 10;
5744       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5745       break;
5746     case VariantXiangqi:
5747       pieces = XiangqiArray;
5748       gameInfo.boardWidth  = 9;
5749       gameInfo.boardHeight = 10;
5750       nrCastlingRights = 0;
5751       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5752       break;
5753     case VariantShogi:
5754       pieces = ShogiArray;
5755       gameInfo.boardWidth  = 9;
5756       gameInfo.boardHeight = 9;
5757       gameInfo.holdingsSize = 7;
5758       nrCastlingRights = 0;
5759       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5760       break;
5761     case VariantCourier:
5762       pieces = CourierArray;
5763       gameInfo.boardWidth  = 12;
5764       nrCastlingRights = 0;
5765       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5766       break;
5767     case VariantKnightmate:
5768       pieces = KnightmateArray;
5769       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5770       break;
5771     case VariantSpartan:
5772       pieces = SpartanArray;
5773       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5774       break;
5775     case VariantFairy:
5776       pieces = fairyArray;
5777       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5778       break;
5779     case VariantGreat:
5780       pieces = GreatArray;
5781       gameInfo.boardWidth = 10;
5782       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5783       gameInfo.holdingsSize = 8;
5784       break;
5785     case VariantSuper:
5786       pieces = FIDEArray;
5787       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5788       gameInfo.holdingsSize = 8;
5789       startedFromSetupPosition = TRUE;
5790       break;
5791     case VariantCrazyhouse:
5792     case VariantBughouse:
5793       pieces = FIDEArray;
5794       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5795       gameInfo.holdingsSize = 5;
5796       break;
5797     case VariantWildCastle:
5798       pieces = FIDEArray;
5799       /* !!?shuffle with kings guaranteed to be on d or e file */
5800       shuffleOpenings = 1;
5801       break;
5802     case VariantNoCastle:
5803       pieces = FIDEArray;
5804       nrCastlingRights = 0;
5805       /* !!?unconstrained back-rank shuffle */
5806       shuffleOpenings = 1;
5807       break;
5808     }
5809
5810     overrule = 0;
5811     if(appData.NrFiles >= 0) {
5812         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5813         gameInfo.boardWidth = appData.NrFiles;
5814     }
5815     if(appData.NrRanks >= 0) {
5816         gameInfo.boardHeight = appData.NrRanks;
5817     }
5818     if(appData.holdingsSize >= 0) {
5819         i = appData.holdingsSize;
5820         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5821         gameInfo.holdingsSize = i;
5822     }
5823     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5824     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5825         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5826
5827     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5828     if(pawnRow < 1) pawnRow = 1;
5829     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5830
5831     /* User pieceToChar list overrules defaults */
5832     if(appData.pieceToCharTable != NULL)
5833         SetCharTable(pieceToChar, appData.pieceToCharTable);
5834
5835     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5836
5837         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5838             s = (ChessSquare) 0; /* account holding counts in guard band */
5839         for( i=0; i<BOARD_HEIGHT; i++ )
5840             initialPosition[i][j] = s;
5841
5842         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5843         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5844         initialPosition[pawnRow][j] = WhitePawn;
5845         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5846         if(gameInfo.variant == VariantXiangqi) {
5847             if(j&1) {
5848                 initialPosition[pawnRow][j] =
5849                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5850                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5851                    initialPosition[2][j] = WhiteCannon;
5852                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5853                 }
5854             }
5855         }
5856         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5857     }
5858     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5859
5860             j=BOARD_LEFT+1;
5861             initialPosition[1][j] = WhiteBishop;
5862             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5863             j=BOARD_RGHT-2;
5864             initialPosition[1][j] = WhiteRook;
5865             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5866     }
5867
5868     if( nrCastlingRights == -1) {
5869         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5870         /*       This sets default castling rights from none to normal corners   */
5871         /* Variants with other castling rights must set them themselves above    */
5872         nrCastlingRights = 6;
5873
5874         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5875         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5876         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5877         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5878         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5879         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5880      }
5881
5882      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5883      if(gameInfo.variant == VariantGreat) { // promotion commoners
5884         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5885         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5886         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5887         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5888      }
5889      if( gameInfo.variant == VariantSChess ) {
5890       initialPosition[1][0] = BlackMarshall;
5891       initialPosition[2][0] = BlackAngel;
5892       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5893       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5894       initialPosition[1][1] = initialPosition[2][1] = 
5895       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5896      }
5897   if (appData.debugMode) {
5898     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5899   }
5900     if(shuffleOpenings) {
5901         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5902         startedFromSetupPosition = TRUE;
5903     }
5904     if(startedFromPositionFile) {
5905       /* [HGM] loadPos: use PositionFile for every new game */
5906       CopyBoard(initialPosition, filePosition);
5907       for(i=0; i<nrCastlingRights; i++)
5908           initialRights[i] = filePosition[CASTLING][i];
5909       startedFromSetupPosition = TRUE;
5910     }
5911
5912     CopyBoard(boards[0], initialPosition);
5913
5914     if(oldx != gameInfo.boardWidth ||
5915        oldy != gameInfo.boardHeight ||
5916        oldv != gameInfo.variant ||
5917        oldh != gameInfo.holdingsWidth
5918                                          )
5919             InitDrawingSizes(-2 ,0);
5920
5921     oldv = gameInfo.variant;
5922     if (redraw)
5923       DrawPosition(TRUE, boards[currentMove]);
5924 }
5925
5926 void
5927 SendBoard(cps, moveNum)
5928      ChessProgramState *cps;
5929      int moveNum;
5930 {
5931     char message[MSG_SIZ];
5932
5933     if (cps->useSetboard) {
5934       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5935       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5936       SendToProgram(message, cps);
5937       free(fen);
5938
5939     } else {
5940       ChessSquare *bp;
5941       int i, j;
5942       /* Kludge to set black to move, avoiding the troublesome and now
5943        * deprecated "black" command.
5944        */
5945       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5946         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5947
5948       SendToProgram("edit\n", cps);
5949       SendToProgram("#\n", cps);
5950       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5951         bp = &boards[moveNum][i][BOARD_LEFT];
5952         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5953           if ((int) *bp < (int) BlackPawn) {
5954             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5955                     AAA + j, ONE + i);
5956             if(message[0] == '+' || message[0] == '~') {
5957               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5958                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5959                         AAA + j, ONE + i);
5960             }
5961             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5962                 message[1] = BOARD_RGHT   - 1 - j + '1';
5963                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5964             }
5965             SendToProgram(message, cps);
5966           }
5967         }
5968       }
5969
5970       SendToProgram("c\n", cps);
5971       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5972         bp = &boards[moveNum][i][BOARD_LEFT];
5973         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5974           if (((int) *bp != (int) EmptySquare)
5975               && ((int) *bp >= (int) BlackPawn)) {
5976             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5977                     AAA + j, ONE + i);
5978             if(message[0] == '+' || message[0] == '~') {
5979               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5980                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5981                         AAA + j, ONE + i);
5982             }
5983             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5984                 message[1] = BOARD_RGHT   - 1 - j + '1';
5985                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5986             }
5987             SendToProgram(message, cps);
5988           }
5989         }
5990       }
5991
5992       SendToProgram(".\n", cps);
5993     }
5994     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5995 }
5996
5997 ChessSquare
5998 DefaultPromoChoice(int white)
5999 {
6000     ChessSquare result;
6001     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6002         result = WhiteFerz; // no choice
6003     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6004         result= WhiteKing; // in Suicide Q is the last thing we want
6005     else if(gameInfo.variant == VariantSpartan)
6006         result = white ? WhiteQueen : WhiteAngel;
6007     else result = WhiteQueen;
6008     if(!white) result = WHITE_TO_BLACK result;
6009     return result;
6010 }
6011
6012 static int autoQueen; // [HGM] oneclick
6013
6014 int
6015 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6016 {
6017     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6018     /* [HGM] add Shogi promotions */
6019     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6020     ChessSquare piece;
6021     ChessMove moveType;
6022     Boolean premove;
6023
6024     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6025     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6026
6027     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6028       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6029         return FALSE;
6030
6031     piece = boards[currentMove][fromY][fromX];
6032     if(gameInfo.variant == VariantShogi) {
6033         promotionZoneSize = BOARD_HEIGHT/3;
6034         highestPromotingPiece = (int)WhiteFerz;
6035     } else if(gameInfo.variant == VariantMakruk) {
6036         promotionZoneSize = 3;
6037     }
6038
6039     // Treat Lance as Pawn when it is not representing Amazon
6040     if(gameInfo.variant != VariantSuper) {
6041         if(piece == WhiteLance) piece = WhitePawn; else
6042         if(piece == BlackLance) piece = BlackPawn;
6043     }
6044
6045     // next weed out all moves that do not touch the promotion zone at all
6046     if((int)piece >= BlackPawn) {
6047         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6048              return FALSE;
6049         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6050     } else {
6051         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6052            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6053     }
6054
6055     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6056
6057     // weed out mandatory Shogi promotions
6058     if(gameInfo.variant == VariantShogi) {
6059         if(piece >= BlackPawn) {
6060             if(toY == 0 && piece == BlackPawn ||
6061                toY == 0 && piece == BlackQueen ||
6062                toY <= 1 && piece == BlackKnight) {
6063                 *promoChoice = '+';
6064                 return FALSE;
6065             }
6066         } else {
6067             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6068                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6069                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6070                 *promoChoice = '+';
6071                 return FALSE;
6072             }
6073         }
6074     }
6075
6076     // weed out obviously illegal Pawn moves
6077     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6078         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6079         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6080         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6081         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6082         // note we are not allowed to test for valid (non-)capture, due to premove
6083     }
6084
6085     // we either have a choice what to promote to, or (in Shogi) whether to promote
6086     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6087         *promoChoice = PieceToChar(BlackFerz);  // no choice
6088         return FALSE;
6089     }
6090     // no sense asking what we must promote to if it is going to explode...
6091     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6092         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6093         return FALSE;
6094     }
6095     // give caller the default choice even if we will not make it
6096     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6097     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6098     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6099                            && gameInfo.variant != VariantShogi
6100                            && gameInfo.variant != VariantSuper) return FALSE;
6101     if(autoQueen) return FALSE; // predetermined
6102
6103     // suppress promotion popup on illegal moves that are not premoves
6104     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6105               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6106     if(appData.testLegality && !premove) {
6107         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6108                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6109         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6110             return FALSE;
6111     }
6112
6113     return TRUE;
6114 }
6115
6116 int
6117 InPalace(row, column)
6118      int row, column;
6119 {   /* [HGM] for Xiangqi */
6120     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6121          column < (BOARD_WIDTH + 4)/2 &&
6122          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6123     return FALSE;
6124 }
6125
6126 int
6127 PieceForSquare (x, y)
6128      int x;
6129      int y;
6130 {
6131   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6132      return -1;
6133   else
6134      return boards[currentMove][y][x];
6135 }
6136
6137 int
6138 OKToStartUserMove(x, y)
6139      int x, y;
6140 {
6141     ChessSquare from_piece;
6142     int white_piece;
6143
6144     if (matchMode) return FALSE;
6145     if (gameMode == EditPosition) return TRUE;
6146
6147     if (x >= 0 && y >= 0)
6148       from_piece = boards[currentMove][y][x];
6149     else
6150       from_piece = EmptySquare;
6151
6152     if (from_piece == EmptySquare) return FALSE;
6153
6154     white_piece = (int)from_piece >= (int)WhitePawn &&
6155       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6156
6157     switch (gameMode) {
6158       case PlayFromGameFile:
6159       case AnalyzeFile:
6160       case TwoMachinesPlay:
6161       case EndOfGame:
6162         return FALSE;
6163
6164       case IcsObserving:
6165       case IcsIdle:
6166         return FALSE;
6167
6168       case MachinePlaysWhite:
6169       case IcsPlayingBlack:
6170         if (appData.zippyPlay) return FALSE;
6171         if (white_piece) {
6172             DisplayMoveError(_("You are playing Black"));
6173             return FALSE;
6174         }
6175         break;
6176
6177       case MachinePlaysBlack:
6178       case IcsPlayingWhite:
6179         if (appData.zippyPlay) return FALSE;
6180         if (!white_piece) {
6181             DisplayMoveError(_("You are playing White"));
6182             return FALSE;
6183         }
6184         break;
6185
6186       case EditGame:
6187         if (!white_piece && WhiteOnMove(currentMove)) {
6188             DisplayMoveError(_("It is White's turn"));
6189             return FALSE;
6190         }
6191         if (white_piece && !WhiteOnMove(currentMove)) {
6192             DisplayMoveError(_("It is Black's turn"));
6193             return FALSE;
6194         }
6195         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6196             /* Editing correspondence game history */
6197             /* Could disallow this or prompt for confirmation */
6198             cmailOldMove = -1;
6199         }
6200         break;
6201
6202       case BeginningOfGame:
6203         if (appData.icsActive) return FALSE;
6204         if (!appData.noChessProgram) {
6205             if (!white_piece) {
6206                 DisplayMoveError(_("You are playing White"));
6207                 return FALSE;
6208             }
6209         }
6210         break;
6211
6212       case Training:
6213         if (!white_piece && WhiteOnMove(currentMove)) {
6214             DisplayMoveError(_("It is White's turn"));
6215             return FALSE;
6216         }
6217         if (white_piece && !WhiteOnMove(currentMove)) {
6218             DisplayMoveError(_("It is Black's turn"));
6219             return FALSE;
6220         }
6221         break;
6222
6223       default:
6224       case IcsExamining:
6225         break;
6226     }
6227     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6228         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6229         && gameMode != AnalyzeFile && gameMode != Training) {
6230         DisplayMoveError(_("Displayed position is not current"));
6231         return FALSE;
6232     }
6233     return TRUE;
6234 }
6235
6236 Boolean
6237 OnlyMove(int *x, int *y, Boolean captures) {
6238     DisambiguateClosure cl;
6239     if (appData.zippyPlay) return FALSE;
6240     switch(gameMode) {
6241       case MachinePlaysBlack:
6242       case IcsPlayingWhite:
6243       case BeginningOfGame:
6244         if(!WhiteOnMove(currentMove)) return FALSE;
6245         break;
6246       case MachinePlaysWhite:
6247       case IcsPlayingBlack:
6248         if(WhiteOnMove(currentMove)) return FALSE;
6249         break;
6250       case EditGame:
6251         break;
6252       default:
6253         return FALSE;
6254     }
6255     cl.pieceIn = EmptySquare;
6256     cl.rfIn = *y;
6257     cl.ffIn = *x;
6258     cl.rtIn = -1;
6259     cl.ftIn = -1;
6260     cl.promoCharIn = NULLCHAR;
6261     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6262     if( cl.kind == NormalMove ||
6263         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6264         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6265         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6266       fromX = cl.ff;
6267       fromY = cl.rf;
6268       *x = cl.ft;
6269       *y = cl.rt;
6270       return TRUE;
6271     }
6272     if(cl.kind != ImpossibleMove) return FALSE;
6273     cl.pieceIn = EmptySquare;
6274     cl.rfIn = -1;
6275     cl.ffIn = -1;
6276     cl.rtIn = *y;
6277     cl.ftIn = *x;
6278     cl.promoCharIn = NULLCHAR;
6279     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6280     if( cl.kind == NormalMove ||
6281         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6282         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6283         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6284       fromX = cl.ff;
6285       fromY = cl.rf;
6286       *x = cl.ft;
6287       *y = cl.rt;
6288       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6289       return TRUE;
6290     }
6291     return FALSE;
6292 }
6293
6294 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6295 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6296 int lastLoadGameUseList = FALSE;
6297 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6298 ChessMove lastLoadGameStart = EndOfFile;
6299
6300 void
6301 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6302      int fromX, fromY, toX, toY;
6303      int promoChar;
6304 {
6305     ChessMove moveType;
6306     ChessSquare pdown, pup;
6307
6308     /* Check if the user is playing in turn.  This is complicated because we
6309        let the user "pick up" a piece before it is his turn.  So the piece he
6310        tried to pick up may have been captured by the time he puts it down!
6311        Therefore we use the color the user is supposed to be playing in this
6312        test, not the color of the piece that is currently on the starting
6313        square---except in EditGame mode, where the user is playing both
6314        sides; fortunately there the capture race can't happen.  (It can
6315        now happen in IcsExamining mode, but that's just too bad.  The user
6316        will get a somewhat confusing message in that case.)
6317        */
6318
6319     switch (gameMode) {
6320       case PlayFromGameFile:
6321       case AnalyzeFile:
6322       case TwoMachinesPlay:
6323       case EndOfGame:
6324       case IcsObserving:
6325       case IcsIdle:
6326         /* We switched into a game mode where moves are not accepted,
6327            perhaps while the mouse button was down. */
6328         return;
6329
6330       case MachinePlaysWhite:
6331         /* User is moving for Black */
6332         if (WhiteOnMove(currentMove)) {
6333             DisplayMoveError(_("It is White's turn"));
6334             return;
6335         }
6336         break;
6337
6338       case MachinePlaysBlack:
6339         /* User is moving for White */
6340         if (!WhiteOnMove(currentMove)) {
6341             DisplayMoveError(_("It is Black's turn"));
6342             return;
6343         }
6344         break;
6345
6346       case EditGame:
6347       case IcsExamining:
6348       case BeginningOfGame:
6349       case AnalyzeMode:
6350       case Training:
6351         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6352         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6353             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6354             /* User is moving for Black */
6355             if (WhiteOnMove(currentMove)) {
6356                 DisplayMoveError(_("It is White's turn"));
6357                 return;
6358             }
6359         } else {
6360             /* User is moving for White */
6361             if (!WhiteOnMove(currentMove)) {
6362                 DisplayMoveError(_("It is Black's turn"));
6363                 return;
6364             }
6365         }
6366         break;
6367
6368       case IcsPlayingBlack:
6369         /* User is moving for Black */
6370         if (WhiteOnMove(currentMove)) {
6371             if (!appData.premove) {
6372                 DisplayMoveError(_("It is White's turn"));
6373             } else if (toX >= 0 && toY >= 0) {
6374                 premoveToX = toX;
6375                 premoveToY = toY;
6376                 premoveFromX = fromX;
6377                 premoveFromY = fromY;
6378                 premovePromoChar = promoChar;
6379                 gotPremove = 1;
6380                 if (appData.debugMode)
6381                     fprintf(debugFP, "Got premove: fromX %d,"
6382                             "fromY %d, toX %d, toY %d\n",
6383                             fromX, fromY, toX, toY);
6384             }
6385             return;
6386         }
6387         break;
6388
6389       case IcsPlayingWhite:
6390         /* User is moving for White */
6391         if (!WhiteOnMove(currentMove)) {
6392             if (!appData.premove) {
6393                 DisplayMoveError(_("It is Black's turn"));
6394             } else if (toX >= 0 && toY >= 0) {
6395                 premoveToX = toX;
6396                 premoveToY = toY;
6397                 premoveFromX = fromX;
6398                 premoveFromY = fromY;
6399                 premovePromoChar = promoChar;
6400                 gotPremove = 1;
6401                 if (appData.debugMode)
6402                     fprintf(debugFP, "Got premove: fromX %d,"
6403                             "fromY %d, toX %d, toY %d\n",
6404                             fromX, fromY, toX, toY);
6405             }
6406             return;
6407         }
6408         break;
6409
6410       default:
6411         break;
6412
6413       case EditPosition:
6414         /* EditPosition, empty square, or different color piece;
6415            click-click move is possible */
6416         if (toX == -2 || toY == -2) {
6417             boards[0][fromY][fromX] = EmptySquare;
6418             DrawPosition(FALSE, boards[currentMove]);
6419             return;
6420         } else if (toX >= 0 && toY >= 0) {
6421             boards[0][toY][toX] = boards[0][fromY][fromX];
6422             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6423                 if(boards[0][fromY][0] != EmptySquare) {
6424                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6425                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6426                 }
6427             } else
6428             if(fromX == BOARD_RGHT+1) {
6429                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6430                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6431                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6432                 }
6433             } else
6434             boards[0][fromY][fromX] = EmptySquare;
6435             DrawPosition(FALSE, boards[currentMove]);
6436             return;
6437         }
6438         return;
6439     }
6440
6441     if(toX < 0 || toY < 0) return;
6442     pdown = boards[currentMove][fromY][fromX];
6443     pup = boards[currentMove][toY][toX];
6444
6445     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6446     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6447          if( pup != EmptySquare ) return;
6448          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6449            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6450                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6451            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6452            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6453            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6454            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6455          fromY = DROP_RANK;
6456     }
6457
6458     /* [HGM] always test for legality, to get promotion info */
6459     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6460                                          fromY, fromX, toY, toX, promoChar);
6461     /* [HGM] but possibly ignore an IllegalMove result */
6462     if (appData.testLegality) {
6463         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6464             DisplayMoveError(_("Illegal move"));
6465             return;
6466         }
6467     }
6468
6469     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6470 }
6471
6472 /* Common tail of UserMoveEvent and DropMenuEvent */
6473 int
6474 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6475      ChessMove moveType;
6476      int fromX, fromY, toX, toY;
6477      /*char*/int promoChar;
6478 {
6479     char *bookHit = 0;
6480
6481     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6482         // [HGM] superchess: suppress promotions to non-available piece
6483         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6484         if(WhiteOnMove(currentMove)) {
6485             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6486         } else {
6487             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6488         }
6489     }
6490
6491     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6492        move type in caller when we know the move is a legal promotion */
6493     if(moveType == NormalMove && promoChar)
6494         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6495
6496     /* [HGM] <popupFix> The following if has been moved here from
6497        UserMoveEvent(). Because it seemed to belong here (why not allow
6498        piece drops in training games?), and because it can only be
6499        performed after it is known to what we promote. */
6500     if (gameMode == Training) {
6501       /* compare the move played on the board to the next move in the
6502        * game. If they match, display the move and the opponent's response.
6503        * If they don't match, display an error message.
6504        */
6505       int saveAnimate;
6506       Board testBoard;
6507       CopyBoard(testBoard, boards[currentMove]);
6508       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6509
6510       if (CompareBoards(testBoard, boards[currentMove+1])) {
6511         ForwardInner(currentMove+1);
6512
6513         /* Autoplay the opponent's response.
6514          * if appData.animate was TRUE when Training mode was entered,
6515          * the response will be animated.
6516          */
6517         saveAnimate = appData.animate;
6518         appData.animate = animateTraining;
6519         ForwardInner(currentMove+1);
6520         appData.animate = saveAnimate;
6521
6522         /* check for the end of the game */
6523         if (currentMove >= forwardMostMove) {
6524           gameMode = PlayFromGameFile;
6525           ModeHighlight();
6526           SetTrainingModeOff();
6527           DisplayInformation(_("End of game"));
6528         }
6529       } else {
6530         DisplayError(_("Incorrect move"), 0);
6531       }
6532       return 1;
6533     }
6534
6535   /* Ok, now we know that the move is good, so we can kill
6536      the previous line in Analysis Mode */
6537   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6538                                 && currentMove < forwardMostMove) {
6539     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6540     else forwardMostMove = currentMove;
6541   }
6542
6543   /* If we need the chess program but it's dead, restart it */
6544   ResurrectChessProgram();
6545
6546   /* A user move restarts a paused game*/
6547   if (pausing)
6548     PauseEvent();
6549
6550   thinkOutput[0] = NULLCHAR;
6551
6552   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6553
6554   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6555     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6556     return 1;
6557   }
6558
6559   if (gameMode == BeginningOfGame) {
6560     if (appData.noChessProgram) {
6561       gameMode = EditGame;
6562       SetGameInfo();
6563     } else {
6564       char buf[MSG_SIZ];
6565       gameMode = MachinePlaysBlack;
6566       StartClocks();
6567       SetGameInfo();
6568       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6569       DisplayTitle(buf);
6570       if (first.sendName) {
6571         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6572         SendToProgram(buf, &first);
6573       }
6574       StartClocks();
6575     }
6576     ModeHighlight();
6577   }
6578
6579   /* Relay move to ICS or chess engine */
6580   if (appData.icsActive) {
6581     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6582         gameMode == IcsExamining) {
6583       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6584         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6585         SendToICS("draw ");
6586         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6587       }
6588       // also send plain move, in case ICS does not understand atomic claims
6589       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6590       ics_user_moved = 1;
6591     }
6592   } else {
6593     if (first.sendTime && (gameMode == BeginningOfGame ||
6594                            gameMode == MachinePlaysWhite ||
6595                            gameMode == MachinePlaysBlack)) {
6596       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6597     }
6598     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6599          // [HGM] book: if program might be playing, let it use book
6600         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6601         first.maybeThinking = TRUE;
6602     } else SendMoveToProgram(forwardMostMove-1, &first);
6603     if (currentMove == cmailOldMove + 1) {
6604       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6605     }
6606   }
6607
6608   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6609
6610   switch (gameMode) {
6611   case EditGame:
6612     if(appData.testLegality)
6613     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6614     case MT_NONE:
6615     case MT_CHECK:
6616       break;
6617     case MT_CHECKMATE:
6618     case MT_STAINMATE:
6619       if (WhiteOnMove(currentMove)) {
6620         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6621       } else {
6622         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6623       }
6624       break;
6625     case MT_STALEMATE:
6626       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6627       break;
6628     }
6629     break;
6630
6631   case MachinePlaysBlack:
6632   case MachinePlaysWhite:
6633     /* disable certain menu options while machine is thinking */
6634     SetMachineThinkingEnables();
6635     break;
6636
6637   default:
6638     break;
6639   }
6640
6641   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6642   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6643
6644   if(bookHit) { // [HGM] book: simulate book reply
6645         static char bookMove[MSG_SIZ]; // a bit generous?
6646
6647         programStats.nodes = programStats.depth = programStats.time =
6648         programStats.score = programStats.got_only_move = 0;
6649         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6650
6651         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6652         strcat(bookMove, bookHit);
6653         HandleMachineMove(bookMove, &first);
6654   }
6655   return 1;
6656 }
6657
6658 void
6659 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6660      Board board;
6661      int flags;
6662      ChessMove kind;
6663      int rf, ff, rt, ft;
6664      VOIDSTAR closure;
6665 {
6666     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6667     Markers *m = (Markers *) closure;
6668     if(rf == fromY && ff == fromX)
6669         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6670                          || kind == WhiteCapturesEnPassant
6671                          || kind == BlackCapturesEnPassant);
6672     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6673 }
6674
6675 void
6676 MarkTargetSquares(int clear)
6677 {
6678   int x, y;
6679   if(!appData.markers || !appData.highlightDragging ||
6680      !appData.testLegality || gameMode == EditPosition) return;
6681   if(clear) {
6682     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6683   } else {
6684     int capt = 0;
6685     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6686     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6687       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6688       if(capt)
6689       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6690     }
6691   }
6692   DrawPosition(TRUE, NULL);
6693 }
6694
6695 int
6696 Explode(Board board, int fromX, int fromY, int toX, int toY)
6697 {
6698     if(gameInfo.variant == VariantAtomic &&
6699        (board[toY][toX] != EmptySquare ||                     // capture?
6700         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6701                          board[fromY][fromX] == BlackPawn   )
6702       )) {
6703         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6704         return TRUE;
6705     }
6706     return FALSE;
6707 }
6708
6709 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6710
6711 int CanPromote(ChessSquare piece, int y)
6712 {
6713         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6714         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6715         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6716            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6717            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6718                                                   gameInfo.variant == VariantMakruk) return FALSE;
6719         return (piece == BlackPawn && y == 1 ||
6720                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6721                 piece == BlackLance && y == 1 ||
6722                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6723 }
6724
6725 void LeftClick(ClickType clickType, int xPix, int yPix)
6726 {
6727     int x, y;
6728     Boolean saveAnimate;
6729     static int second = 0, promotionChoice = 0, clearFlag = 0;
6730     char promoChoice = NULLCHAR;
6731     ChessSquare piece;
6732
6733     if(appData.seekGraph && appData.icsActive && loggedOn &&
6734         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6735         SeekGraphClick(clickType, xPix, yPix, 0);
6736         return;
6737     }
6738
6739     if (clickType == Press) ErrorPopDown();
6740     MarkTargetSquares(1);
6741
6742     x = EventToSquare(xPix, BOARD_WIDTH);
6743     y = EventToSquare(yPix, BOARD_HEIGHT);
6744     if (!flipView && y >= 0) {
6745         y = BOARD_HEIGHT - 1 - y;
6746     }
6747     if (flipView && x >= 0) {
6748         x = BOARD_WIDTH - 1 - x;
6749     }
6750
6751     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6752         defaultPromoChoice = promoSweep;
6753         promoSweep = EmptySquare;   // terminate sweep
6754         promoDefaultAltered = TRUE;
6755         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6756     }
6757
6758     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6759         if(clickType == Release) return; // ignore upclick of click-click destination
6760         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6761         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6762         if(gameInfo.holdingsWidth &&
6763                 (WhiteOnMove(currentMove)
6764                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6765                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6766             // click in right holdings, for determining promotion piece
6767             ChessSquare p = boards[currentMove][y][x];
6768             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6769             if(p != EmptySquare) {
6770                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6771                 fromX = fromY = -1;
6772                 return;
6773             }
6774         }
6775         DrawPosition(FALSE, boards[currentMove]);
6776         return;
6777     }
6778
6779     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6780     if(clickType == Press
6781             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6782               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6783               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6784         return;
6785
6786     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6787         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6788
6789     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6790         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6791                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6792         defaultPromoChoice = DefaultPromoChoice(side);
6793     }
6794
6795     autoQueen = appData.alwaysPromoteToQueen;
6796
6797     if (fromX == -1) {
6798       int originalY = y;
6799       gatingPiece = EmptySquare;
6800       if (clickType != Press) {
6801         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6802             DragPieceEnd(xPix, yPix); dragging = 0;
6803             DrawPosition(FALSE, NULL);
6804         }
6805         return;
6806       }
6807       fromX = x; fromY = y;
6808       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6809          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6810          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6811             /* First square */
6812             if (OKToStartUserMove(fromX, fromY)) {
6813                 second = 0;
6814                 MarkTargetSquares(0);
6815                 DragPieceBegin(xPix, yPix); dragging = 1;
6816                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6817                     promoSweep = defaultPromoChoice;
6818                     selectFlag = 0; lastX = xPix; lastY = yPix;
6819                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6820                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6821                 }
6822                 if (appData.highlightDragging) {
6823                     SetHighlights(fromX, fromY, -1, -1);
6824                 }
6825             } else fromX = fromY = -1;
6826             return;
6827         }
6828     }
6829
6830     /* fromX != -1 */
6831     if (clickType == Press && gameMode != EditPosition) {
6832         ChessSquare fromP;
6833         ChessSquare toP;
6834         int frc;
6835
6836         // ignore off-board to clicks
6837         if(y < 0 || x < 0) return;
6838
6839         /* Check if clicking again on the same color piece */
6840         fromP = boards[currentMove][fromY][fromX];
6841         toP = boards[currentMove][y][x];
6842         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6843         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6844              WhitePawn <= toP && toP <= WhiteKing &&
6845              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6846              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6847             (BlackPawn <= fromP && fromP <= BlackKing &&
6848              BlackPawn <= toP && toP <= BlackKing &&
6849              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6850              !(fromP == BlackKing && toP == BlackRook && frc))) {
6851             /* Clicked again on same color piece -- changed his mind */
6852             second = (x == fromX && y == fromY);
6853             promoDefaultAltered = FALSE;
6854            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6855             if (appData.highlightDragging) {
6856                 SetHighlights(x, y, -1, -1);
6857             } else {
6858                 ClearHighlights();
6859             }
6860             if (OKToStartUserMove(x, y)) {
6861                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6862                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6863                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6864                  gatingPiece = boards[currentMove][fromY][fromX];
6865                 else gatingPiece = EmptySquare;
6866                 fromX = x;
6867                 fromY = y; dragging = 1;
6868                 MarkTargetSquares(0);
6869                 DragPieceBegin(xPix, yPix);
6870                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6871                     promoSweep = defaultPromoChoice;
6872                     selectFlag = 0; lastX = xPix; lastY = yPix;
6873                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6874                 }
6875             }
6876            }
6877            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6878            second = FALSE; 
6879         }
6880         // ignore clicks on holdings
6881         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6882     }
6883
6884     if (clickType == Release && x == fromX && y == fromY) {
6885         DragPieceEnd(xPix, yPix); dragging = 0;
6886         if(clearFlag) {
6887             // a deferred attempt to click-click move an empty square on top of a piece
6888             boards[currentMove][y][x] = EmptySquare;
6889             ClearHighlights();
6890             DrawPosition(FALSE, boards[currentMove]);
6891             fromX = fromY = -1; clearFlag = 0;
6892             return;
6893         }
6894         if (appData.animateDragging) {
6895             /* Undo animation damage if any */
6896             DrawPosition(FALSE, NULL);
6897         }
6898         if (second) {
6899             /* Second up/down in same square; just abort move */
6900             second = 0;
6901             fromX = fromY = -1;
6902             gatingPiece = EmptySquare;
6903             ClearHighlights();
6904             gotPremove = 0;
6905             ClearPremoveHighlights();
6906         } else {
6907             /* First upclick in same square; start click-click mode */
6908             SetHighlights(x, y, -1, -1);
6909         }
6910         return;
6911     }
6912
6913     clearFlag = 0;
6914
6915     /* we now have a different from- and (possibly off-board) to-square */
6916     /* Completed move */
6917     toX = x;
6918     toY = y;
6919     saveAnimate = appData.animate;
6920     if (clickType == Press) {
6921         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6922             // must be Edit Position mode with empty-square selected
6923             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6924             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6925             return;
6926         }
6927         /* Finish clickclick move */
6928         if (appData.animate || appData.highlightLastMove) {
6929             SetHighlights(fromX, fromY, toX, toY);
6930         } else {
6931             ClearHighlights();
6932         }
6933     } else {
6934         /* Finish drag move */
6935         if (appData.highlightLastMove) {
6936             SetHighlights(fromX, fromY, toX, toY);
6937         } else {
6938             ClearHighlights();
6939         }
6940         DragPieceEnd(xPix, yPix); dragging = 0;
6941         /* Don't animate move and drag both */
6942         appData.animate = FALSE;
6943     }
6944
6945     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6946     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6947         ChessSquare piece = boards[currentMove][fromY][fromX];
6948         if(gameMode == EditPosition && piece != EmptySquare &&
6949            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6950             int n;
6951
6952             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6953                 n = PieceToNumber(piece - (int)BlackPawn);
6954                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6955                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6956                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6957             } else
6958             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6959                 n = PieceToNumber(piece);
6960                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6961                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6962                 boards[currentMove][n][BOARD_WIDTH-2]++;
6963             }
6964             boards[currentMove][fromY][fromX] = EmptySquare;
6965         }
6966         ClearHighlights();
6967         fromX = fromY = -1;
6968         DrawPosition(TRUE, boards[currentMove]);
6969         return;
6970     }
6971
6972     // off-board moves should not be highlighted
6973     if(x < 0 || y < 0) ClearHighlights();
6974
6975     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6976
6977     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6978         SetHighlights(fromX, fromY, toX, toY);
6979         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6980             // [HGM] super: promotion to captured piece selected from holdings
6981             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6982             promotionChoice = TRUE;
6983             // kludge follows to temporarily execute move on display, without promoting yet
6984             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6985             boards[currentMove][toY][toX] = p;
6986             DrawPosition(FALSE, boards[currentMove]);
6987             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6988             boards[currentMove][toY][toX] = q;
6989             DisplayMessage("Click in holdings to choose piece", "");
6990             return;
6991         }
6992         PromotionPopUp();
6993     } else {
6994         int oldMove = currentMove;
6995         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6996         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6997         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6998         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6999            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7000             DrawPosition(TRUE, boards[currentMove]);
7001         fromX = fromY = -1;
7002     }
7003     appData.animate = saveAnimate;
7004     if (appData.animate || appData.animateDragging) {
7005         /* Undo animation damage if needed */
7006         DrawPosition(FALSE, NULL);
7007     }
7008 }
7009
7010 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7011 {   // front-end-free part taken out of PieceMenuPopup
7012     int whichMenu; int xSqr, ySqr;
7013
7014     if(seekGraphUp) { // [HGM] seekgraph
7015         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7016         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7017         return -2;
7018     }
7019
7020     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7021          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7022         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7023         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7024         if(action == Press)   {
7025             originalFlip = flipView;
7026             flipView = !flipView; // temporarily flip board to see game from partners perspective
7027             DrawPosition(TRUE, partnerBoard);
7028             DisplayMessage(partnerStatus, "");
7029             partnerUp = TRUE;
7030         } else if(action == Release) {
7031             flipView = originalFlip;
7032             DrawPosition(TRUE, boards[currentMove]);
7033             partnerUp = FALSE;
7034         }
7035         return -2;
7036     }
7037
7038     xSqr = EventToSquare(x, BOARD_WIDTH);
7039     ySqr = EventToSquare(y, BOARD_HEIGHT);
7040     if (action == Release) {
7041         if(pieceSweep != EmptySquare) {
7042             EditPositionMenuEvent(pieceSweep, toX, toY);
7043             pieceSweep = EmptySquare;
7044         } else UnLoadPV(); // [HGM] pv
7045     }
7046     if (action != Press) return -2; // return code to be ignored
7047     switch (gameMode) {
7048       case IcsExamining:
7049         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7050       case EditPosition:
7051         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7052         if (xSqr < 0 || ySqr < 0) return -1;
7053         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7054         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7055         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7056         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7057         NextPiece(0);
7058         return -2;\r
7059       case IcsObserving:
7060         if(!appData.icsEngineAnalyze) return -1;
7061       case IcsPlayingWhite:
7062       case IcsPlayingBlack:
7063         if(!appData.zippyPlay) goto noZip;
7064       case AnalyzeMode:
7065       case AnalyzeFile:
7066       case MachinePlaysWhite:
7067       case MachinePlaysBlack:
7068       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7069         if (!appData.dropMenu) {
7070           LoadPV(x, y);
7071           return 2; // flag front-end to grab mouse events
7072         }
7073         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7074            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7075       case EditGame:
7076       noZip:
7077         if (xSqr < 0 || ySqr < 0) return -1;
7078         if (!appData.dropMenu || appData.testLegality &&
7079             gameInfo.variant != VariantBughouse &&
7080             gameInfo.variant != VariantCrazyhouse) return -1;
7081         whichMenu = 1; // drop menu
7082         break;
7083       default:
7084         return -1;
7085     }
7086
7087     if (((*fromX = xSqr) < 0) ||
7088         ((*fromY = ySqr) < 0)) {
7089         *fromX = *fromY = -1;
7090         return -1;
7091     }
7092     if (flipView)
7093       *fromX = BOARD_WIDTH - 1 - *fromX;
7094     else
7095       *fromY = BOARD_HEIGHT - 1 - *fromY;
7096
7097     return whichMenu;
7098 }
7099
7100 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7101 {
7102 //    char * hint = lastHint;
7103     FrontEndProgramStats stats;
7104
7105     stats.which = cps == &first ? 0 : 1;
7106     stats.depth = cpstats->depth;
7107     stats.nodes = cpstats->nodes;
7108     stats.score = cpstats->score;
7109     stats.time = cpstats->time;
7110     stats.pv = cpstats->movelist;
7111     stats.hint = lastHint;
7112     stats.an_move_index = 0;
7113     stats.an_move_count = 0;
7114
7115     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7116         stats.hint = cpstats->move_name;
7117         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7118         stats.an_move_count = cpstats->nr_moves;
7119     }
7120
7121     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
7122
7123     SetProgramStats( &stats );
7124 }
7125
7126 #define MAXPLAYERS 500
7127
7128 char *
7129 TourneyStandings(int display)
7130 {
7131     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7132     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7133     char result, *p, *names[MAXPLAYERS];
7134
7135     names[0] = p = strdup(appData.participants);
7136     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7137
7138     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7139
7140     while(result = appData.results[nr]) {
7141         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7142         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7143         wScore = bScore = 0;
7144         switch(result) {
7145           case '+': wScore = 2; break;
7146           case '-': bScore = 2; break;
7147           case '=': wScore = bScore = 1; break;
7148           case ' ':
7149           case '*': return strdup("busy"); // tourney not finished
7150         }
7151         score[w] += wScore;
7152         score[b] += bScore;
7153         games[w]++;
7154         games[b]++;
7155         nr++;
7156     }
7157     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7158     for(w=0; w<nPlayers; w++) {
7159         bScore = -1;
7160         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7161         ranking[w] = b; points[w] = bScore; score[b] = -2;
7162     }
7163     p = malloc(nPlayers*34+1);
7164     for(w=0; w<nPlayers && w<display; w++)
7165         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7166     free(names[0]);
7167     return p;
7168 }
7169
7170 void
7171 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7172 {       // count all piece types
7173         int p, f, r;
7174         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7175         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7176         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7177                 p = board[r][f];
7178                 pCnt[p]++;
7179                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7180                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7181                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7182                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7183                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7184                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7185         }
7186 }
7187
7188 int
7189 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7190 {
7191         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7192         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7193
7194         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7195         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7196         if(myPawns == 2 && nMine == 3) // KPP
7197             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7198         if(myPawns == 1 && nMine == 2) // KP
7199             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7200         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7201             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7202         if(myPawns) return FALSE;
7203         if(pCnt[WhiteRook+side])
7204             return pCnt[BlackRook-side] ||
7205                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7206                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7207                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7208         if(pCnt[WhiteCannon+side]) {
7209             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7210             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7211         }
7212         if(pCnt[WhiteKnight+side])
7213             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7214         return FALSE;
7215 }
7216
7217 int
7218 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7219 {
7220         VariantClass v = gameInfo.variant;
7221
7222         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7223         if(v == VariantShatranj) return TRUE; // always winnable through baring
7224         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7225         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7226
7227         if(v == VariantXiangqi) {
7228                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7229
7230                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7231                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7232                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7233                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7234                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7235                 if(stale) // we have at least one last-rank P plus perhaps C
7236                     return majors // KPKX
7237                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7238                 else // KCA*E*
7239                     return pCnt[WhiteFerz+side] // KCAK
7240                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7241                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7242                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7243
7244         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7245                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7246
7247                 if(nMine == 1) return FALSE; // bare King
7248                 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
7249                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7250                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7251                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7252                 if(pCnt[WhiteKnight+side])
7253                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7254                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7255                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7256                 if(nBishops)
7257                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7258                 if(pCnt[WhiteAlfil+side])
7259                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7260                 if(pCnt[WhiteWazir+side])
7261                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7262         }
7263
7264         return TRUE;
7265 }
7266
7267 int
7268 Adjudicate(ChessProgramState *cps)
7269 {       // [HGM] some adjudications useful with buggy engines
7270         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7271         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7272         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7273         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7274         int k, count = 0; static int bare = 1;
7275         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7276         Boolean canAdjudicate = !appData.icsActive;
7277
7278         // most tests only when we understand the game, i.e. legality-checking on
7279             if( appData.testLegality )
7280             {   /* [HGM] Some more adjudications for obstinate engines */
7281                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7282                 static int moveCount = 6;
7283                 ChessMove result;
7284                 char *reason = NULL;
7285
7286                 /* Count what is on board. */
7287                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7288
7289                 /* Some material-based adjudications that have to be made before stalemate test */
7290                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7291                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7292                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7293                      if(canAdjudicate && appData.checkMates) {
7294                          if(engineOpponent)
7295                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7296                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7297                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7298                          return 1;
7299                      }
7300                 }
7301
7302                 /* Bare King in Shatranj (loses) or Losers (wins) */
7303                 if( nrW == 1 || nrB == 1) {
7304                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7305                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7306                      if(canAdjudicate && appData.checkMates) {
7307                          if(engineOpponent)
7308                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7309                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7310                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7311                          return 1;
7312                      }
7313                   } else
7314                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7315                   {    /* bare King */
7316                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7317                         if(canAdjudicate && appData.checkMates) {
7318                             /* but only adjudicate if adjudication enabled */
7319                             if(engineOpponent)
7320                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7321                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7322                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7323                             return 1;
7324                         }
7325                   }
7326                 } else bare = 1;
7327
7328
7329             // don't wait for engine to announce game end if we can judge ourselves
7330             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7331               case MT_CHECK:
7332                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7333                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7334                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7335                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7336                             checkCnt++;
7337                         if(checkCnt >= 2) {
7338                             reason = "Xboard adjudication: 3rd check";
7339                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7340                             break;
7341                         }
7342                     }
7343                 }
7344               case MT_NONE:
7345               default:
7346                 break;
7347               case MT_STALEMATE:
7348               case MT_STAINMATE:
7349                 reason = "Xboard adjudication: Stalemate";
7350                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7351                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7352                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7353                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7354                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7355                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7356                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7357                                                                         EP_CHECKMATE : EP_WINS);
7358                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7359                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7360                 }
7361                 break;
7362               case MT_CHECKMATE:
7363                 reason = "Xboard adjudication: Checkmate";
7364                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7365                 break;
7366             }
7367
7368                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7369                     case EP_STALEMATE:
7370                         result = GameIsDrawn; break;
7371                     case EP_CHECKMATE:
7372                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7373                     case EP_WINS:
7374                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7375                     default:
7376                         result = EndOfFile;
7377                 }
7378                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7379                     if(engineOpponent)
7380                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7381                     GameEnds( result, reason, GE_XBOARD );
7382                     return 1;
7383                 }
7384
7385                 /* Next absolutely insufficient mating material. */
7386                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7387                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7388                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7389
7390                      /* always flag draws, for judging claims */
7391                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7392
7393                      if(canAdjudicate && appData.materialDraws) {
7394                          /* but only adjudicate them if adjudication enabled */
7395                          if(engineOpponent) {
7396                            SendToProgram("force\n", engineOpponent); // suppress reply
7397                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7398                          }
7399                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7400                          return 1;
7401                      }
7402                 }
7403
7404                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7405                 if(gameInfo.variant == VariantXiangqi ?
7406                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7407                  : nrW + nrB == 4 &&
7408                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7409                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7410                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7411                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7412                    ) ) {
7413                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7414                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7415                           if(engineOpponent) {
7416                             SendToProgram("force\n", engineOpponent); // suppress reply
7417                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7418                           }
7419                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7420                           return 1;
7421                      }
7422                 } else moveCount = 6;
7423             }
7424         if (appData.debugMode) { int i;
7425             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7426                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7427                     appData.drawRepeats);
7428             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7429               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7430
7431         }
7432
7433         // Repetition draws and 50-move rule can be applied independently of legality testing
7434
7435                 /* Check for rep-draws */
7436                 count = 0;
7437                 for(k = forwardMostMove-2;
7438                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7439                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7440                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7441                     k-=2)
7442                 {   int rights=0;
7443                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7444                         /* compare castling rights */
7445                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7446                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7447                                 rights++; /* King lost rights, while rook still had them */
7448                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7449                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7450                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7451                                    rights++; /* but at least one rook lost them */
7452                         }
7453                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7454                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7455                                 rights++;
7456                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7457                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7458                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7459                                    rights++;
7460                         }
7461                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7462                             && appData.drawRepeats > 1) {
7463                              /* adjudicate after user-specified nr of repeats */
7464                              int result = GameIsDrawn;
7465                              char *details = "XBoard adjudication: repetition draw";
7466                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7467                                 // [HGM] xiangqi: check for forbidden perpetuals
7468                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7469                                 for(m=forwardMostMove; m>k; m-=2) {
7470                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7471                                         ourPerpetual = 0; // the current mover did not always check
7472                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7473                                         hisPerpetual = 0; // the opponent did not always check
7474                                 }
7475                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7476                                                                         ourPerpetual, hisPerpetual);
7477                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7478                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7479                                     details = "Xboard adjudication: perpetual checking";
7480                                 } else
7481                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7482                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7483                                 } else
7484                                 // Now check for perpetual chases
7485                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7486                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7487                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7488                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7489                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7490                                         details = "Xboard adjudication: perpetual chasing";
7491                                     } else
7492                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7493                                         break; // Abort repetition-checking loop.
7494                                 }
7495                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7496                              }
7497                              if(engineOpponent) {
7498                                SendToProgram("force\n", engineOpponent); // suppress reply
7499                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7500                              }
7501                              GameEnds( result, details, GE_XBOARD );
7502                              return 1;
7503                         }
7504                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7505                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7506                     }
7507                 }
7508
7509                 /* Now we test for 50-move draws. Determine ply count */
7510                 count = forwardMostMove;
7511                 /* look for last irreversble move */
7512                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7513                     count--;
7514                 /* if we hit starting position, add initial plies */
7515                 if( count == backwardMostMove )
7516                     count -= initialRulePlies;
7517                 count = forwardMostMove - count;
7518                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7519                         // adjust reversible move counter for checks in Xiangqi
7520                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7521                         if(i < backwardMostMove) i = backwardMostMove;
7522                         while(i <= forwardMostMove) {
7523                                 lastCheck = inCheck; // check evasion does not count
7524                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7525                                 if(inCheck || lastCheck) count--; // check does not count
7526                                 i++;
7527                         }
7528                 }
7529                 if( count >= 100)
7530                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7531                          /* this is used to judge if draw claims are legal */
7532                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7533                          if(engineOpponent) {
7534                            SendToProgram("force\n", engineOpponent); // suppress reply
7535                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7536                          }
7537                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7538                          return 1;
7539                 }
7540
7541                 /* if draw offer is pending, treat it as a draw claim
7542                  * when draw condition present, to allow engines a way to
7543                  * claim draws before making their move to avoid a race
7544                  * condition occurring after their move
7545                  */
7546                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7547                          char *p = NULL;
7548                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7549                              p = "Draw claim: 50-move rule";
7550                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7551                              p = "Draw claim: 3-fold repetition";
7552                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7553                              p = "Draw claim: insufficient mating material";
7554                          if( p != NULL && canAdjudicate) {
7555                              if(engineOpponent) {
7556                                SendToProgram("force\n", engineOpponent); // suppress reply
7557                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7558                              }
7559                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7560                              return 1;
7561                          }
7562                 }
7563
7564                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7565                     if(engineOpponent) {
7566                       SendToProgram("force\n", engineOpponent); // suppress reply
7567                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7568                     }
7569                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7570                     return 1;
7571                 }
7572         return 0;
7573 }
7574
7575 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7576 {   // [HGM] book: this routine intercepts moves to simulate book replies
7577     char *bookHit = NULL;
7578
7579     //first determine if the incoming move brings opponent into his book
7580     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7581         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7582     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7583     if(bookHit != NULL && !cps->bookSuspend) {
7584         // make sure opponent is not going to reply after receiving move to book position
7585         SendToProgram("force\n", cps);
7586         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7587     }
7588     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7589     // now arrange restart after book miss
7590     if(bookHit) {
7591         // after a book hit we never send 'go', and the code after the call to this routine
7592         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7593         char buf[MSG_SIZ], *move = bookHit;
7594         if(cps->useSAN) {
7595             int fromX, fromY, toX, toY;
7596             char promoChar;
7597             ChessMove moveType;
7598             move = buf + 30;
7599             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7600                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7601                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7602                                     PosFlags(forwardMostMove),
7603                                     fromY, fromX, toY, toX, promoChar, move);
7604             } else {
7605                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7606                 bookHit = NULL;
7607             }
7608         }
7609         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7610         SendToProgram(buf, cps);
7611         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7612     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7613         SendToProgram("go\n", cps);
7614         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7615     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7616         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7617             SendToProgram("go\n", cps);
7618         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7619     }
7620     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7621 }
7622
7623 char *savedMessage;
7624 ChessProgramState *savedState;
7625 void DeferredBookMove(void)
7626 {
7627         if(savedState->lastPing != savedState->lastPong)
7628                     ScheduleDelayedEvent(DeferredBookMove, 10);
7629         else
7630         HandleMachineMove(savedMessage, savedState);
7631 }
7632
7633 void
7634 HandleMachineMove(message, cps)
7635      char *message;
7636      ChessProgramState *cps;
7637 {
7638     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7639     char realname[MSG_SIZ];
7640     int fromX, fromY, toX, toY;
7641     ChessMove moveType;
7642     char promoChar;
7643     char *p;
7644     int machineWhite;
7645     char *bookHit;
7646
7647     cps->userError = 0;
7648
7649 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7650     /*
7651      * Kludge to ignore BEL characters
7652      */
7653     while (*message == '\007') message++;
7654
7655     /*
7656      * [HGM] engine debug message: ignore lines starting with '#' character
7657      */
7658     if(cps->debug && *message == '#') return;
7659
7660     /*
7661      * Look for book output
7662      */
7663     if (cps == &first && bookRequested) {
7664         if (message[0] == '\t' || message[0] == ' ') {
7665             /* Part of the book output is here; append it */
7666             strcat(bookOutput, message);
7667             strcat(bookOutput, "  \n");
7668             return;
7669         } else if (bookOutput[0] != NULLCHAR) {
7670             /* All of book output has arrived; display it */
7671             char *p = bookOutput;
7672             while (*p != NULLCHAR) {
7673                 if (*p == '\t') *p = ' ';
7674                 p++;
7675             }
7676             DisplayInformation(bookOutput);
7677             bookRequested = FALSE;
7678             /* Fall through to parse the current output */
7679         }
7680     }
7681
7682     /*
7683      * Look for machine move.
7684      */
7685     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7686         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7687     {
7688         /* This method is only useful on engines that support ping */
7689         if (cps->lastPing != cps->lastPong) {
7690           if (gameMode == BeginningOfGame) {
7691             /* Extra move from before last new; ignore */
7692             if (appData.debugMode) {
7693                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7694             }
7695           } else {
7696             if (appData.debugMode) {
7697                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7698                         cps->which, gameMode);
7699             }
7700
7701             SendToProgram("undo\n", cps);
7702           }
7703           return;
7704         }
7705
7706         switch (gameMode) {
7707           case BeginningOfGame:
7708             /* Extra move from before last reset; ignore */
7709             if (appData.debugMode) {
7710                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7711             }
7712             return;
7713
7714           case EndOfGame:
7715           case IcsIdle:
7716           default:
7717             /* Extra move after we tried to stop.  The mode test is
7718                not a reliable way of detecting this problem, but it's
7719                the best we can do on engines that don't support ping.
7720             */
7721             if (appData.debugMode) {
7722                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7723                         cps->which, gameMode);
7724             }
7725             SendToProgram("undo\n", cps);
7726             return;
7727
7728           case MachinePlaysWhite:
7729           case IcsPlayingWhite:
7730             machineWhite = TRUE;
7731             break;
7732
7733           case MachinePlaysBlack:
7734           case IcsPlayingBlack:
7735             machineWhite = FALSE;
7736             break;
7737
7738           case TwoMachinesPlay:
7739             machineWhite = (cps->twoMachinesColor[0] == 'w');
7740             break;
7741         }
7742         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7743             if (appData.debugMode) {
7744                 fprintf(debugFP,
7745                         "Ignoring move out of turn by %s, gameMode %d"
7746                         ", forwardMost %d\n",
7747                         cps->which, gameMode, forwardMostMove);
7748             }
7749             return;
7750         }
7751
7752     if (appData.debugMode) { int f = forwardMostMove;
7753         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7754                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7755                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7756     }
7757         if(cps->alphaRank) AlphaRank(machineMove, 4);
7758         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7759                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7760             /* Machine move could not be parsed; ignore it. */
7761           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7762                     machineMove, _(cps->which));
7763             DisplayError(buf1, 0);
7764             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7765                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7766             if (gameMode == TwoMachinesPlay) {
7767               GameEnds(machineWhite ? BlackWins : WhiteWins,
7768                        buf1, GE_XBOARD);
7769             }
7770             return;
7771         }
7772
7773         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7774         /* So we have to redo legality test with true e.p. status here,  */
7775         /* to make sure an illegal e.p. capture does not slip through,   */
7776         /* to cause a forfeit on a justified illegal-move complaint      */
7777         /* of the opponent.                                              */
7778         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7779            ChessMove moveType;
7780            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7781                              fromY, fromX, toY, toX, promoChar);
7782             if (appData.debugMode) {
7783                 int i;
7784                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7785                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7786                 fprintf(debugFP, "castling rights\n");
7787             }
7788             if(moveType == IllegalMove) {
7789               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7790                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7791                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7792                            buf1, GE_XBOARD);
7793                 return;
7794            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7795            /* [HGM] Kludge to handle engines that send FRC-style castling
7796               when they shouldn't (like TSCP-Gothic) */
7797            switch(moveType) {
7798              case WhiteASideCastleFR:
7799              case BlackASideCastleFR:
7800                toX+=2;
7801                currentMoveString[2]++;
7802                break;
7803              case WhiteHSideCastleFR:
7804              case BlackHSideCastleFR:
7805                toX--;
7806                currentMoveString[2]--;
7807                break;
7808              default: ; // nothing to do, but suppresses warning of pedantic compilers
7809            }
7810         }
7811         hintRequested = FALSE;
7812         lastHint[0] = NULLCHAR;
7813         bookRequested = FALSE;
7814         /* Program may be pondering now */
7815         cps->maybeThinking = TRUE;
7816         if (cps->sendTime == 2) cps->sendTime = 1;
7817         if (cps->offeredDraw) cps->offeredDraw--;
7818
7819         /* [AS] Save move info*/
7820         pvInfoList[ forwardMostMove ].score = programStats.score;
7821         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7822         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7823
7824         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7825
7826         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7827         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7828             int count = 0;
7829
7830             while( count < adjudicateLossPlies ) {
7831                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7832
7833                 if( count & 1 ) {
7834                     score = -score; /* Flip score for winning side */
7835                 }
7836
7837                 if( score > adjudicateLossThreshold ) {
7838                     break;
7839                 }
7840
7841                 count++;
7842             }
7843
7844             if( count >= adjudicateLossPlies ) {
7845                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7846
7847                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7848                     "Xboard adjudication",
7849                     GE_XBOARD );
7850
7851                 return;
7852             }
7853         }
7854
7855         if(Adjudicate(cps)) {
7856             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7857             return; // [HGM] adjudicate: for all automatic game ends
7858         }
7859
7860 #if ZIPPY
7861         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7862             first.initDone) {
7863           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7864                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7865                 SendToICS("draw ");
7866                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7867           }
7868           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7869           ics_user_moved = 1;
7870           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7871                 char buf[3*MSG_SIZ];
7872
7873                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7874                         programStats.score / 100.,
7875                         programStats.depth,
7876                         programStats.time / 100.,
7877                         (unsigned int)programStats.nodes,
7878                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7879                         programStats.movelist);
7880                 SendToICS(buf);
7881 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7882           }
7883         }
7884 #endif
7885
7886         /* [AS] Clear stats for next move */
7887         ClearProgramStats();
7888         thinkOutput[0] = NULLCHAR;
7889         hiddenThinkOutputState = 0;
7890
7891         bookHit = NULL;
7892         if (gameMode == TwoMachinesPlay) {
7893             /* [HGM] relaying draw offers moved to after reception of move */
7894             /* and interpreting offer as claim if it brings draw condition */
7895             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7896                 SendToProgram("draw\n", cps->other);
7897             }
7898             if (cps->other->sendTime) {
7899                 SendTimeRemaining(cps->other,
7900                                   cps->other->twoMachinesColor[0] == 'w');
7901             }
7902             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7903             if (firstMove && !bookHit) {
7904                 firstMove = FALSE;
7905                 if (cps->other->useColors) {
7906                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7907                 }
7908                 SendToProgram("go\n", cps->other);
7909             }
7910             cps->other->maybeThinking = TRUE;
7911         }
7912
7913         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7914
7915         if (!pausing && appData.ringBellAfterMoves) {
7916             RingBell();
7917         }
7918
7919         /*
7920          * Reenable menu items that were disabled while
7921          * machine was thinking
7922          */
7923         if (gameMode != TwoMachinesPlay)
7924             SetUserThinkingEnables();
7925
7926         // [HGM] book: after book hit opponent has received move and is now in force mode
7927         // force the book reply into it, and then fake that it outputted this move by jumping
7928         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7929         if(bookHit) {
7930                 static char bookMove[MSG_SIZ]; // a bit generous?
7931
7932                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7933                 strcat(bookMove, bookHit);
7934                 message = bookMove;
7935                 cps = cps->other;
7936                 programStats.nodes = programStats.depth = programStats.time =
7937                 programStats.score = programStats.got_only_move = 0;
7938                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7939
7940                 if(cps->lastPing != cps->lastPong) {
7941                     savedMessage = message; // args for deferred call
7942                     savedState = cps;
7943                     ScheduleDelayedEvent(DeferredBookMove, 10);
7944                     return;
7945                 }
7946                 goto FakeBookMove;
7947         }
7948
7949         return;
7950     }
7951
7952     /* Set special modes for chess engines.  Later something general
7953      *  could be added here; for now there is just one kludge feature,
7954      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7955      *  when "xboard" is given as an interactive command.
7956      */
7957     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7958         cps->useSigint = FALSE;
7959         cps->useSigterm = FALSE;
7960     }
7961     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7962       ParseFeatures(message+8, cps);
7963       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7964     }
7965
7966     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7967       int dummy, s=6; char buf[MSG_SIZ];
7968       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7969       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7970       ParseFEN(boards[0], &dummy, message+s);
7971       DrawPosition(TRUE, boards[0]);
7972       startedFromSetupPosition = TRUE;
7973       return;
7974     }
7975     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7976      * want this, I was asked to put it in, and obliged.
7977      */
7978     if (!strncmp(message, "setboard ", 9)) {
7979         Board initial_position;
7980
7981         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7982
7983         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7984             DisplayError(_("Bad FEN received from engine"), 0);
7985             return ;
7986         } else {
7987            Reset(TRUE, FALSE);
7988            CopyBoard(boards[0], initial_position);
7989            initialRulePlies = FENrulePlies;
7990            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7991            else gameMode = MachinePlaysBlack;
7992            DrawPosition(FALSE, boards[currentMove]);
7993         }
7994         return;
7995     }
7996
7997     /*
7998      * Look for communication commands
7999      */
8000     if (!strncmp(message, "telluser ", 9)) {
8001         if(message[9] == '\\' && message[10] == '\\')
8002             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8003         DisplayNote(message + 9);
8004         return;
8005     }
8006     if (!strncmp(message, "tellusererror ", 14)) {
8007         cps->userError = 1;
8008         if(message[14] == '\\' && message[15] == '\\')
8009             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8010         DisplayError(message + 14, 0);
8011         return;
8012     }
8013     if (!strncmp(message, "tellopponent ", 13)) {
8014       if (appData.icsActive) {
8015         if (loggedOn) {
8016           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8017           SendToICS(buf1);
8018         }
8019       } else {
8020         DisplayNote(message + 13);
8021       }
8022       return;
8023     }
8024     if (!strncmp(message, "tellothers ", 11)) {
8025       if (appData.icsActive) {
8026         if (loggedOn) {
8027           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8028           SendToICS(buf1);
8029         }
8030       }
8031       return;
8032     }
8033     if (!strncmp(message, "tellall ", 8)) {
8034       if (appData.icsActive) {
8035         if (loggedOn) {
8036           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8037           SendToICS(buf1);
8038         }
8039       } else {
8040         DisplayNote(message + 8);
8041       }
8042       return;
8043     }
8044     if (strncmp(message, "warning", 7) == 0) {
8045         /* Undocumented feature, use tellusererror in new code */
8046         DisplayError(message, 0);
8047         return;
8048     }
8049     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8050         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8051         strcat(realname, " query");
8052         AskQuestion(realname, buf2, buf1, cps->pr);
8053         return;
8054     }
8055     /* Commands from the engine directly to ICS.  We don't allow these to be
8056      *  sent until we are logged on. Crafty kibitzes have been known to
8057      *  interfere with the login process.
8058      */
8059     if (loggedOn) {
8060         if (!strncmp(message, "tellics ", 8)) {
8061             SendToICS(message + 8);
8062             SendToICS("\n");
8063             return;
8064         }
8065         if (!strncmp(message, "tellicsnoalias ", 15)) {
8066             SendToICS(ics_prefix);
8067             SendToICS(message + 15);
8068             SendToICS("\n");
8069             return;
8070         }
8071         /* The following are for backward compatibility only */
8072         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8073             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8074             SendToICS(ics_prefix);
8075             SendToICS(message);
8076             SendToICS("\n");
8077             return;
8078         }
8079     }
8080     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8081         return;
8082     }
8083     /*
8084      * If the move is illegal, cancel it and redraw the board.
8085      * Also deal with other error cases.  Matching is rather loose
8086      * here to accommodate engines written before the spec.
8087      */
8088     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8089         strncmp(message, "Error", 5) == 0) {
8090         if (StrStr(message, "name") ||
8091             StrStr(message, "rating") || StrStr(message, "?") ||
8092             StrStr(message, "result") || StrStr(message, "board") ||
8093             StrStr(message, "bk") || StrStr(message, "computer") ||
8094             StrStr(message, "variant") || StrStr(message, "hint") ||
8095             StrStr(message, "random") || StrStr(message, "depth") ||
8096             StrStr(message, "accepted")) {
8097             return;
8098         }
8099         if (StrStr(message, "protover")) {
8100           /* Program is responding to input, so it's apparently done
8101              initializing, and this error message indicates it is
8102              protocol version 1.  So we don't need to wait any longer
8103              for it to initialize and send feature commands. */
8104           FeatureDone(cps, 1);
8105           cps->protocolVersion = 1;
8106           return;
8107         }
8108         cps->maybeThinking = FALSE;
8109
8110         if (StrStr(message, "draw")) {
8111             /* Program doesn't have "draw" command */
8112             cps->sendDrawOffers = 0;
8113             return;
8114         }
8115         if (cps->sendTime != 1 &&
8116             (StrStr(message, "time") || StrStr(message, "otim"))) {
8117           /* Program apparently doesn't have "time" or "otim" command */
8118           cps->sendTime = 0;
8119           return;
8120         }
8121         if (StrStr(message, "analyze")) {
8122             cps->analysisSupport = FALSE;
8123             cps->analyzing = FALSE;
8124             Reset(FALSE, TRUE);
8125             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8126             DisplayError(buf2, 0);
8127             return;
8128         }
8129         if (StrStr(message, "(no matching move)st")) {
8130           /* Special kludge for GNU Chess 4 only */
8131           cps->stKludge = TRUE;
8132           SendTimeControl(cps, movesPerSession, timeControl,
8133                           timeIncrement, appData.searchDepth,
8134                           searchTime);
8135           return;
8136         }
8137         if (StrStr(message, "(no matching move)sd")) {
8138           /* Special kludge for GNU Chess 4 only */
8139           cps->sdKludge = TRUE;
8140           SendTimeControl(cps, movesPerSession, timeControl,
8141                           timeIncrement, appData.searchDepth,
8142                           searchTime);
8143           return;
8144         }
8145         if (!StrStr(message, "llegal")) {
8146             return;
8147         }
8148         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8149             gameMode == IcsIdle) return;
8150         if (forwardMostMove <= backwardMostMove) return;
8151         if (pausing) PauseEvent();
8152       if(appData.forceIllegal) {
8153             // [HGM] illegal: machine refused move; force position after move into it
8154           SendToProgram("force\n", cps);
8155           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8156                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8157                 // when black is to move, while there might be nothing on a2 or black
8158                 // might already have the move. So send the board as if white has the move.
8159                 // But first we must change the stm of the engine, as it refused the last move
8160                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8161                 if(WhiteOnMove(forwardMostMove)) {
8162                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8163                     SendBoard(cps, forwardMostMove); // kludgeless board
8164                 } else {
8165                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8166                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8167                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8168                 }
8169           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8170             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8171                  gameMode == TwoMachinesPlay)
8172               SendToProgram("go\n", cps);
8173             return;
8174       } else
8175         if (gameMode == PlayFromGameFile) {
8176             /* Stop reading this game file */
8177             gameMode = EditGame;
8178             ModeHighlight();
8179         }
8180         /* [HGM] illegal-move claim should forfeit game when Xboard */
8181         /* only passes fully legal moves                            */
8182         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8183             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8184                                 "False illegal-move claim", GE_XBOARD );
8185             return; // do not take back move we tested as valid
8186         }
8187         currentMove = forwardMostMove-1;
8188         DisplayMove(currentMove-1); /* before DisplayMoveError */
8189         SwitchClocks(forwardMostMove-1); // [HGM] race
8190         DisplayBothClocks();
8191         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8192                 parseList[currentMove], _(cps->which));
8193         DisplayMoveError(buf1);
8194         DrawPosition(FALSE, boards[currentMove]);
8195         return;
8196     }
8197     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8198         /* Program has a broken "time" command that
8199            outputs a string not ending in newline.
8200            Don't use it. */
8201         cps->sendTime = 0;
8202     }
8203
8204     /*
8205      * If chess program startup fails, exit with an error message.
8206      * Attempts to recover here are futile.
8207      */
8208     if ((StrStr(message, "unknown host") != NULL)
8209         || (StrStr(message, "No remote directory") != NULL)
8210         || (StrStr(message, "not found") != NULL)
8211         || (StrStr(message, "No such file") != NULL)
8212         || (StrStr(message, "can't alloc") != NULL)
8213         || (StrStr(message, "Permission denied") != NULL)) {
8214
8215         cps->maybeThinking = FALSE;
8216         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8217                 _(cps->which), cps->program, cps->host, message);
8218         RemoveInputSource(cps->isr);
8219         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8220             if(cps == &first) appData.noChessProgram = TRUE;
8221             DisplayError(buf1, 0);
8222         }
8223         return;
8224     }
8225
8226     /*
8227      * Look for hint output
8228      */
8229     if (sscanf(message, "Hint: %s", buf1) == 1) {
8230         if (cps == &first && hintRequested) {
8231             hintRequested = FALSE;
8232             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8233                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8234                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8235                                     PosFlags(forwardMostMove),
8236                                     fromY, fromX, toY, toX, promoChar, buf1);
8237                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8238                 DisplayInformation(buf2);
8239             } else {
8240                 /* Hint move could not be parsed!? */
8241               snprintf(buf2, sizeof(buf2),
8242                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8243                         buf1, _(cps->which));
8244                 DisplayError(buf2, 0);
8245             }
8246         } else {
8247           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8248         }
8249         return;
8250     }
8251
8252     /*
8253      * Ignore other messages if game is not in progress
8254      */
8255     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8256         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8257
8258     /*
8259      * look for win, lose, draw, or draw offer
8260      */
8261     if (strncmp(message, "1-0", 3) == 0) {
8262         char *p, *q, *r = "";
8263         p = strchr(message, '{');
8264         if (p) {
8265             q = strchr(p, '}');
8266             if (q) {
8267                 *q = NULLCHAR;
8268                 r = p + 1;
8269             }
8270         }
8271         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8272         return;
8273     } else if (strncmp(message, "0-1", 3) == 0) {
8274         char *p, *q, *r = "";
8275         p = strchr(message, '{');
8276         if (p) {
8277             q = strchr(p, '}');
8278             if (q) {
8279                 *q = NULLCHAR;
8280                 r = p + 1;
8281             }
8282         }
8283         /* Kludge for Arasan 4.1 bug */
8284         if (strcmp(r, "Black resigns") == 0) {
8285             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8286             return;
8287         }
8288         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8289         return;
8290     } else if (strncmp(message, "1/2", 3) == 0) {
8291         char *p, *q, *r = "";
8292         p = strchr(message, '{');
8293         if (p) {
8294             q = strchr(p, '}');
8295             if (q) {
8296                 *q = NULLCHAR;
8297                 r = p + 1;
8298             }
8299         }
8300
8301         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8302         return;
8303
8304     } else if (strncmp(message, "White resign", 12) == 0) {
8305         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8306         return;
8307     } else if (strncmp(message, "Black resign", 12) == 0) {
8308         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8309         return;
8310     } else if (strncmp(message, "White matches", 13) == 0 ||
8311                strncmp(message, "Black matches", 13) == 0   ) {
8312         /* [HGM] ignore GNUShogi noises */
8313         return;
8314     } else if (strncmp(message, "White", 5) == 0 &&
8315                message[5] != '(' &&
8316                StrStr(message, "Black") == NULL) {
8317         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8318         return;
8319     } else if (strncmp(message, "Black", 5) == 0 &&
8320                message[5] != '(') {
8321         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8322         return;
8323     } else if (strcmp(message, "resign") == 0 ||
8324                strcmp(message, "computer resigns") == 0) {
8325         switch (gameMode) {
8326           case MachinePlaysBlack:
8327           case IcsPlayingBlack:
8328             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8329             break;
8330           case MachinePlaysWhite:
8331           case IcsPlayingWhite:
8332             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8333             break;
8334           case TwoMachinesPlay:
8335             if (cps->twoMachinesColor[0] == 'w')
8336               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8337             else
8338               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8339             break;
8340           default:
8341             /* can't happen */
8342             break;
8343         }
8344         return;
8345     } else if (strncmp(message, "opponent mates", 14) == 0) {
8346         switch (gameMode) {
8347           case MachinePlaysBlack:
8348           case IcsPlayingBlack:
8349             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8350             break;
8351           case MachinePlaysWhite:
8352           case IcsPlayingWhite:
8353             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8354             break;
8355           case TwoMachinesPlay:
8356             if (cps->twoMachinesColor[0] == 'w')
8357               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8358             else
8359               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8360             break;
8361           default:
8362             /* can't happen */
8363             break;
8364         }
8365         return;
8366     } else if (strncmp(message, "computer mates", 14) == 0) {
8367         switch (gameMode) {
8368           case MachinePlaysBlack:
8369           case IcsPlayingBlack:
8370             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8371             break;
8372           case MachinePlaysWhite:
8373           case IcsPlayingWhite:
8374             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8375             break;
8376           case TwoMachinesPlay:
8377             if (cps->twoMachinesColor[0] == 'w')
8378               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8379             else
8380               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8381             break;
8382           default:
8383             /* can't happen */
8384             break;
8385         }
8386         return;
8387     } else if (strncmp(message, "checkmate", 9) == 0) {
8388         if (WhiteOnMove(forwardMostMove)) {
8389             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8390         } else {
8391             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8392         }
8393         return;
8394     } else if (strstr(message, "Draw") != NULL ||
8395                strstr(message, "game is a draw") != NULL) {
8396         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8397         return;
8398     } else if (strstr(message, "offer") != NULL &&
8399                strstr(message, "draw") != NULL) {
8400 #if ZIPPY
8401         if (appData.zippyPlay && first.initDone) {
8402             /* Relay offer to ICS */
8403             SendToICS(ics_prefix);
8404             SendToICS("draw\n");
8405         }
8406 #endif
8407         cps->offeredDraw = 2; /* valid until this engine moves twice */
8408         if (gameMode == TwoMachinesPlay) {
8409             if (cps->other->offeredDraw) {
8410                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8411             /* [HGM] in two-machine mode we delay relaying draw offer      */
8412             /* until after we also have move, to see if it is really claim */
8413             }
8414         } else if (gameMode == MachinePlaysWhite ||
8415                    gameMode == MachinePlaysBlack) {
8416           if (userOfferedDraw) {
8417             DisplayInformation(_("Machine accepts your draw offer"));
8418             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8419           } else {
8420             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8421           }
8422         }
8423     }
8424
8425
8426     /*
8427      * Look for thinking output
8428      */
8429     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8430           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8431                                 ) {
8432         int plylev, mvleft, mvtot, curscore, time;
8433         char mvname[MOVE_LEN];
8434         u64 nodes; // [DM]
8435         char plyext;
8436         int ignore = FALSE;
8437         int prefixHint = FALSE;
8438         mvname[0] = NULLCHAR;
8439
8440         switch (gameMode) {
8441           case MachinePlaysBlack:
8442           case IcsPlayingBlack:
8443             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8444             break;
8445           case MachinePlaysWhite:
8446           case IcsPlayingWhite:
8447             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8448             break;
8449           case AnalyzeMode:
8450           case AnalyzeFile:
8451             break;
8452           case IcsObserving: /* [DM] icsEngineAnalyze */
8453             if (!appData.icsEngineAnalyze) ignore = TRUE;
8454             break;
8455           case TwoMachinesPlay:
8456             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8457                 ignore = TRUE;
8458             }
8459             break;
8460           default:
8461             ignore = TRUE;
8462             break;
8463         }
8464
8465         if (!ignore) {
8466             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8467             buf1[0] = NULLCHAR;
8468             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8469                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8470
8471                 if (plyext != ' ' && plyext != '\t') {
8472                     time *= 100;
8473                 }
8474
8475                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8476                 if( cps->scoreIsAbsolute &&
8477                     ( gameMode == MachinePlaysBlack ||
8478                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8479                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8480                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8481                      !WhiteOnMove(currentMove)
8482                     ) )
8483                 {
8484                     curscore = -curscore;
8485                 }
8486
8487
8488                 tempStats.depth = plylev;
8489                 tempStats.nodes = nodes;
8490                 tempStats.time = time;
8491                 tempStats.score = curscore;
8492                 tempStats.got_only_move = 0;
8493
8494                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8495                         int ticklen;
8496
8497                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8498                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8499                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8500                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8501                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8502                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8503                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8504                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8505                 }
8506
8507                 /* Buffer overflow protection */
8508                 if (buf1[0] != NULLCHAR) {
8509                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8510                         && appData.debugMode) {
8511                         fprintf(debugFP,
8512                                 "PV is too long; using the first %u bytes.\n",
8513                                 (unsigned) sizeof(tempStats.movelist) - 1);
8514                     }
8515
8516                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8517                 } else {
8518                     sprintf(tempStats.movelist, " no PV\n");
8519                 }
8520
8521                 if (tempStats.seen_stat) {
8522                     tempStats.ok_to_send = 1;
8523                 }
8524
8525                 if (strchr(tempStats.movelist, '(') != NULL) {
8526                     tempStats.line_is_book = 1;
8527                     tempStats.nr_moves = 0;
8528                     tempStats.moves_left = 0;
8529                 } else {
8530                     tempStats.line_is_book = 0;
8531                 }
8532
8533                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8534                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8535
8536                 SendProgramStatsToFrontend( cps, &tempStats );
8537
8538                 /*
8539                     [AS] Protect the thinkOutput buffer from overflow... this
8540                     is only useful if buf1 hasn't overflowed first!
8541                 */
8542                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8543                          plylev,
8544                          (gameMode == TwoMachinesPlay ?
8545                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8546                          ((double) curscore) / 100.0,
8547                          prefixHint ? lastHint : "",
8548                          prefixHint ? " " : "" );
8549
8550                 if( buf1[0] != NULLCHAR ) {
8551                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8552
8553                     if( strlen(buf1) > max_len ) {
8554                         if( appData.debugMode) {
8555                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8556                         }
8557                         buf1[max_len+1] = '\0';
8558                     }
8559
8560                     strcat( thinkOutput, buf1 );
8561                 }
8562
8563                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8564                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8565                     DisplayMove(currentMove - 1);
8566                 }
8567                 return;
8568
8569             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8570                 /* crafty (9.25+) says "(only move) <move>"
8571                  * if there is only 1 legal move
8572                  */
8573                 sscanf(p, "(only move) %s", buf1);
8574                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8575                 sprintf(programStats.movelist, "%s (only move)", buf1);
8576                 programStats.depth = 1;
8577                 programStats.nr_moves = 1;
8578                 programStats.moves_left = 1;
8579                 programStats.nodes = 1;
8580                 programStats.time = 1;
8581                 programStats.got_only_move = 1;
8582
8583                 /* Not really, but we also use this member to
8584                    mean "line isn't going to change" (Crafty
8585                    isn't searching, so stats won't change) */
8586                 programStats.line_is_book = 1;
8587
8588                 SendProgramStatsToFrontend( cps, &programStats );
8589
8590                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8591                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8592                     DisplayMove(currentMove - 1);
8593                 }
8594                 return;
8595             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8596                               &time, &nodes, &plylev, &mvleft,
8597                               &mvtot, mvname) >= 5) {
8598                 /* The stat01: line is from Crafty (9.29+) in response
8599                    to the "." command */
8600                 programStats.seen_stat = 1;
8601                 cps->maybeThinking = TRUE;
8602
8603                 if (programStats.got_only_move || !appData.periodicUpdates)
8604                   return;
8605
8606                 programStats.depth = plylev;
8607                 programStats.time = time;
8608                 programStats.nodes = nodes;
8609                 programStats.moves_left = mvleft;
8610                 programStats.nr_moves = mvtot;
8611                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8612                 programStats.ok_to_send = 1;
8613                 programStats.movelist[0] = '\0';
8614
8615                 SendProgramStatsToFrontend( cps, &programStats );
8616
8617                 return;
8618
8619             } else if (strncmp(message,"++",2) == 0) {
8620                 /* Crafty 9.29+ outputs this */
8621                 programStats.got_fail = 2;
8622                 return;
8623
8624             } else if (strncmp(message,"--",2) == 0) {
8625                 /* Crafty 9.29+ outputs this */
8626                 programStats.got_fail = 1;
8627                 return;
8628
8629             } else if (thinkOutput[0] != NULLCHAR &&
8630                        strncmp(message, "    ", 4) == 0) {
8631                 unsigned message_len;
8632
8633                 p = message;
8634                 while (*p && *p == ' ') p++;
8635
8636                 message_len = strlen( p );
8637
8638                 /* [AS] Avoid buffer overflow */
8639                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8640                     strcat(thinkOutput, " ");
8641                     strcat(thinkOutput, p);
8642                 }
8643
8644                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8645                     strcat(programStats.movelist, " ");
8646                     strcat(programStats.movelist, p);
8647                 }
8648
8649                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8650                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8651                     DisplayMove(currentMove - 1);
8652                 }
8653                 return;
8654             }
8655         }
8656         else {
8657             buf1[0] = NULLCHAR;
8658
8659             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8660                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8661             {
8662                 ChessProgramStats cpstats;
8663
8664                 if (plyext != ' ' && plyext != '\t') {
8665                     time *= 100;
8666                 }
8667
8668                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8669                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8670                     curscore = -curscore;
8671                 }
8672
8673                 cpstats.depth = plylev;
8674                 cpstats.nodes = nodes;
8675                 cpstats.time = time;
8676                 cpstats.score = curscore;
8677                 cpstats.got_only_move = 0;
8678                 cpstats.movelist[0] = '\0';
8679
8680                 if (buf1[0] != NULLCHAR) {
8681                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8682                 }
8683
8684                 cpstats.ok_to_send = 0;
8685                 cpstats.line_is_book = 0;
8686                 cpstats.nr_moves = 0;
8687                 cpstats.moves_left = 0;
8688
8689                 SendProgramStatsToFrontend( cps, &cpstats );
8690             }
8691         }
8692     }
8693 }
8694
8695
8696 /* Parse a game score from the character string "game", and
8697    record it as the history of the current game.  The game
8698    score is NOT assumed to start from the standard position.
8699    The display is not updated in any way.
8700    */
8701 void
8702 ParseGameHistory(game)
8703      char *game;
8704 {
8705     ChessMove moveType;
8706     int fromX, fromY, toX, toY, boardIndex;
8707     char promoChar;
8708     char *p, *q;
8709     char buf[MSG_SIZ];
8710
8711     if (appData.debugMode)
8712       fprintf(debugFP, "Parsing game history: %s\n", game);
8713
8714     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8715     gameInfo.site = StrSave(appData.icsHost);
8716     gameInfo.date = PGNDate();
8717     gameInfo.round = StrSave("-");
8718
8719     /* Parse out names of players */
8720     while (*game == ' ') game++;
8721     p = buf;
8722     while (*game != ' ') *p++ = *game++;
8723     *p = NULLCHAR;
8724     gameInfo.white = StrSave(buf);
8725     while (*game == ' ') game++;
8726     p = buf;
8727     while (*game != ' ' && *game != '\n') *p++ = *game++;
8728     *p = NULLCHAR;
8729     gameInfo.black = StrSave(buf);
8730
8731     /* Parse moves */
8732     boardIndex = blackPlaysFirst ? 1 : 0;
8733     yynewstr(game);
8734     for (;;) {
8735         yyboardindex = boardIndex;
8736         moveType = (ChessMove) Myylex();
8737         switch (moveType) {
8738           case IllegalMove:             /* maybe suicide chess, etc. */
8739   if (appData.debugMode) {
8740     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8741     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8742     setbuf(debugFP, NULL);
8743   }
8744           case WhitePromotion:
8745           case BlackPromotion:
8746           case WhiteNonPromotion:
8747           case BlackNonPromotion:
8748           case NormalMove:
8749           case WhiteCapturesEnPassant:
8750           case BlackCapturesEnPassant:
8751           case WhiteKingSideCastle:
8752           case WhiteQueenSideCastle:
8753           case BlackKingSideCastle:
8754           case BlackQueenSideCastle:
8755           case WhiteKingSideCastleWild:
8756           case WhiteQueenSideCastleWild:
8757           case BlackKingSideCastleWild:
8758           case BlackQueenSideCastleWild:
8759           /* PUSH Fabien */
8760           case WhiteHSideCastleFR:
8761           case WhiteASideCastleFR:
8762           case BlackHSideCastleFR:
8763           case BlackASideCastleFR:
8764           /* POP Fabien */
8765             fromX = currentMoveString[0] - AAA;
8766             fromY = currentMoveString[1] - ONE;
8767             toX = currentMoveString[2] - AAA;
8768             toY = currentMoveString[3] - ONE;
8769             promoChar = currentMoveString[4];
8770             break;
8771           case WhiteDrop:
8772           case BlackDrop:
8773             fromX = moveType == WhiteDrop ?
8774               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8775             (int) CharToPiece(ToLower(currentMoveString[0]));
8776             fromY = DROP_RANK;
8777             toX = currentMoveString[2] - AAA;
8778             toY = currentMoveString[3] - ONE;
8779             promoChar = NULLCHAR;
8780             break;
8781           case AmbiguousMove:
8782             /* bug? */
8783             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8784   if (appData.debugMode) {
8785     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8786     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8787     setbuf(debugFP, NULL);
8788   }
8789             DisplayError(buf, 0);
8790             return;
8791           case ImpossibleMove:
8792             /* bug? */
8793             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8794   if (appData.debugMode) {
8795     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8796     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8797     setbuf(debugFP, NULL);
8798   }
8799             DisplayError(buf, 0);
8800             return;
8801           case EndOfFile:
8802             if (boardIndex < backwardMostMove) {
8803                 /* Oops, gap.  How did that happen? */
8804                 DisplayError(_("Gap in move list"), 0);
8805                 return;
8806             }
8807             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8808             if (boardIndex > forwardMostMove) {
8809                 forwardMostMove = boardIndex;
8810             }
8811             return;
8812           case ElapsedTime:
8813             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8814                 strcat(parseList[boardIndex-1], " ");
8815                 strcat(parseList[boardIndex-1], yy_text);
8816             }
8817             continue;
8818           case Comment:
8819           case PGNTag:
8820           case NAG:
8821           default:
8822             /* ignore */
8823             continue;
8824           case WhiteWins:
8825           case BlackWins:
8826           case GameIsDrawn:
8827           case GameUnfinished:
8828             if (gameMode == IcsExamining) {
8829                 if (boardIndex < backwardMostMove) {
8830                     /* Oops, gap.  How did that happen? */
8831                     return;
8832                 }
8833                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8834                 return;
8835             }
8836             gameInfo.result = moveType;
8837             p = strchr(yy_text, '{');
8838             if (p == NULL) p = strchr(yy_text, '(');
8839             if (p == NULL) {
8840                 p = yy_text;
8841                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8842             } else {
8843                 q = strchr(p, *p == '{' ? '}' : ')');
8844                 if (q != NULL) *q = NULLCHAR;
8845                 p++;
8846             }
8847             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8848             gameInfo.resultDetails = StrSave(p);
8849             continue;
8850         }
8851         if (boardIndex >= forwardMostMove &&
8852             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8853             backwardMostMove = blackPlaysFirst ? 1 : 0;
8854             return;
8855         }
8856         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8857                                  fromY, fromX, toY, toX, promoChar,
8858                                  parseList[boardIndex]);
8859         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8860         /* currentMoveString is set as a side-effect of yylex */
8861         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8862         strcat(moveList[boardIndex], "\n");
8863         boardIndex++;
8864         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8865         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8866           case MT_NONE:
8867           case MT_STALEMATE:
8868           default:
8869             break;
8870           case MT_CHECK:
8871             if(gameInfo.variant != VariantShogi)
8872                 strcat(parseList[boardIndex - 1], "+");
8873             break;
8874           case MT_CHECKMATE:
8875           case MT_STAINMATE:
8876             strcat(parseList[boardIndex - 1], "#");
8877             break;
8878         }
8879     }
8880 }
8881
8882
8883 /* Apply a move to the given board  */
8884 void
8885 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8886      int fromX, fromY, toX, toY;
8887      int promoChar;
8888      Board board;
8889 {
8890   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8891   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8892
8893     /* [HGM] compute & store e.p. status and castling rights for new position */
8894     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8895
8896       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8897       oldEP = (signed char)board[EP_STATUS];
8898       board[EP_STATUS] = EP_NONE;
8899
8900       if( board[toY][toX] != EmptySquare )
8901            board[EP_STATUS] = EP_CAPTURE;
8902
8903   if (fromY == DROP_RANK) {
8904         /* must be first */
8905         piece = board[toY][toX] = (ChessSquare) fromX;
8906   } else {
8907       int i;
8908
8909       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8910            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8911                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8912       } else
8913       if( board[fromY][fromX] == WhitePawn ) {
8914            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8915                board[EP_STATUS] = EP_PAWN_MOVE;
8916            if( toY-fromY==2) {
8917                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8918                         gameInfo.variant != VariantBerolina || toX < fromX)
8919                       board[EP_STATUS] = toX | berolina;
8920                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8921                         gameInfo.variant != VariantBerolina || toX > fromX)
8922                       board[EP_STATUS] = toX;
8923            }
8924       } else
8925       if( board[fromY][fromX] == BlackPawn ) {
8926            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8927                board[EP_STATUS] = EP_PAWN_MOVE;
8928            if( toY-fromY== -2) {
8929                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8930                         gameInfo.variant != VariantBerolina || toX < fromX)
8931                       board[EP_STATUS] = toX | berolina;
8932                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8933                         gameInfo.variant != VariantBerolina || toX > fromX)
8934                       board[EP_STATUS] = toX;
8935            }
8936        }
8937
8938        for(i=0; i<nrCastlingRights; i++) {
8939            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8940               board[CASTLING][i] == toX   && castlingRank[i] == toY
8941              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8942        }
8943
8944      if (fromX == toX && fromY == toY) return;
8945
8946      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8947      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8948      if(gameInfo.variant == VariantKnightmate)
8949          king += (int) WhiteUnicorn - (int) WhiteKing;
8950
8951     /* Code added by Tord: */
8952     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8953     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8954         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8955       board[fromY][fromX] = EmptySquare;
8956       board[toY][toX] = EmptySquare;
8957       if((toX > fromX) != (piece == WhiteRook)) {
8958         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8959       } else {
8960         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8961       }
8962     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8963                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8964       board[fromY][fromX] = EmptySquare;
8965       board[toY][toX] = EmptySquare;
8966       if((toX > fromX) != (piece == BlackRook)) {
8967         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8968       } else {
8969         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8970       }
8971     /* End of code added by Tord */
8972
8973     } else if (board[fromY][fromX] == king
8974         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8975         && toY == fromY && toX > fromX+1) {
8976         board[fromY][fromX] = EmptySquare;
8977         board[toY][toX] = king;
8978         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8979         board[fromY][BOARD_RGHT-1] = EmptySquare;
8980     } else if (board[fromY][fromX] == king
8981         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8982                && toY == fromY && toX < fromX-1) {
8983         board[fromY][fromX] = EmptySquare;
8984         board[toY][toX] = king;
8985         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8986         board[fromY][BOARD_LEFT] = EmptySquare;
8987     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8988                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8989                && toY >= BOARD_HEIGHT-promoRank
8990                ) {
8991         /* white pawn promotion */
8992         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8993         if (board[toY][toX] == EmptySquare) {
8994             board[toY][toX] = WhiteQueen;
8995         }
8996         if(gameInfo.variant==VariantBughouse ||
8997            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8998             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8999         board[fromY][fromX] = EmptySquare;
9000     } else if ((fromY == BOARD_HEIGHT-4)
9001                && (toX != fromX)
9002                && gameInfo.variant != VariantXiangqi
9003                && gameInfo.variant != VariantBerolina
9004                && (board[fromY][fromX] == WhitePawn)
9005                && (board[toY][toX] == EmptySquare)) {
9006         board[fromY][fromX] = EmptySquare;
9007         board[toY][toX] = WhitePawn;
9008         captured = board[toY - 1][toX];
9009         board[toY - 1][toX] = EmptySquare;
9010     } else if ((fromY == BOARD_HEIGHT-4)
9011                && (toX == fromX)
9012                && gameInfo.variant == VariantBerolina
9013                && (board[fromY][fromX] == WhitePawn)
9014                && (board[toY][toX] == EmptySquare)) {
9015         board[fromY][fromX] = EmptySquare;
9016         board[toY][toX] = WhitePawn;
9017         if(oldEP & EP_BEROLIN_A) {
9018                 captured = board[fromY][fromX-1];
9019                 board[fromY][fromX-1] = EmptySquare;
9020         }else{  captured = board[fromY][fromX+1];
9021                 board[fromY][fromX+1] = EmptySquare;
9022         }
9023     } else if (board[fromY][fromX] == king
9024         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9025                && toY == fromY && toX > fromX+1) {
9026         board[fromY][fromX] = EmptySquare;
9027         board[toY][toX] = king;
9028         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9029         board[fromY][BOARD_RGHT-1] = EmptySquare;
9030     } else if (board[fromY][fromX] == king
9031         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9032                && toY == fromY && toX < fromX-1) {
9033         board[fromY][fromX] = EmptySquare;
9034         board[toY][toX] = king;
9035         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9036         board[fromY][BOARD_LEFT] = EmptySquare;
9037     } else if (fromY == 7 && fromX == 3
9038                && board[fromY][fromX] == BlackKing
9039                && toY == 7 && toX == 5) {
9040         board[fromY][fromX] = EmptySquare;
9041         board[toY][toX] = BlackKing;
9042         board[fromY][7] = EmptySquare;
9043         board[toY][4] = BlackRook;
9044     } else if (fromY == 7 && fromX == 3
9045                && board[fromY][fromX] == BlackKing
9046                && toY == 7 && toX == 1) {
9047         board[fromY][fromX] = EmptySquare;
9048         board[toY][toX] = BlackKing;
9049         board[fromY][0] = EmptySquare;
9050         board[toY][2] = BlackRook;
9051     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9052                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9053                && toY < promoRank
9054                ) {
9055         /* black pawn promotion */
9056         board[toY][toX] = CharToPiece(ToLower(promoChar));
9057         if (board[toY][toX] == EmptySquare) {
9058             board[toY][toX] = BlackQueen;
9059         }
9060         if(gameInfo.variant==VariantBughouse ||
9061            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9062             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9063         board[fromY][fromX] = EmptySquare;
9064     } else if ((fromY == 3)
9065                && (toX != fromX)
9066                && gameInfo.variant != VariantXiangqi
9067                && gameInfo.variant != VariantBerolina
9068                && (board[fromY][fromX] == BlackPawn)
9069                && (board[toY][toX] == EmptySquare)) {
9070         board[fromY][fromX] = EmptySquare;
9071         board[toY][toX] = BlackPawn;
9072         captured = board[toY + 1][toX];
9073         board[toY + 1][toX] = EmptySquare;
9074     } else if ((fromY == 3)
9075                && (toX == fromX)
9076                && gameInfo.variant == VariantBerolina
9077                && (board[fromY][fromX] == BlackPawn)
9078                && (board[toY][toX] == EmptySquare)) {
9079         board[fromY][fromX] = EmptySquare;
9080         board[toY][toX] = BlackPawn;
9081         if(oldEP & EP_BEROLIN_A) {
9082                 captured = board[fromY][fromX-1];
9083                 board[fromY][fromX-1] = EmptySquare;
9084         }else{  captured = board[fromY][fromX+1];
9085                 board[fromY][fromX+1] = EmptySquare;
9086         }
9087     } else {
9088         board[toY][toX] = board[fromY][fromX];
9089         board[fromY][fromX] = EmptySquare;
9090     }
9091   }
9092
9093     if (gameInfo.holdingsWidth != 0) {
9094
9095       /* !!A lot more code needs to be written to support holdings  */
9096       /* [HGM] OK, so I have written it. Holdings are stored in the */
9097       /* penultimate board files, so they are automaticlly stored   */
9098       /* in the game history.                                       */
9099       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9100                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9101         /* Delete from holdings, by decreasing count */
9102         /* and erasing image if necessary            */
9103         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9104         if(p < (int) BlackPawn) { /* white drop */
9105              p -= (int)WhitePawn;
9106                  p = PieceToNumber((ChessSquare)p);
9107              if(p >= gameInfo.holdingsSize) p = 0;
9108              if(--board[p][BOARD_WIDTH-2] <= 0)
9109                   board[p][BOARD_WIDTH-1] = EmptySquare;
9110              if((int)board[p][BOARD_WIDTH-2] < 0)
9111                         board[p][BOARD_WIDTH-2] = 0;
9112         } else {                  /* black drop */
9113              p -= (int)BlackPawn;
9114                  p = PieceToNumber((ChessSquare)p);
9115              if(p >= gameInfo.holdingsSize) p = 0;
9116              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9117                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9118              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9119                         board[BOARD_HEIGHT-1-p][1] = 0;
9120         }
9121       }
9122       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9123           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9124         /* [HGM] holdings: Add to holdings, if holdings exist */
9125         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9126                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9127                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9128         }
9129         p = (int) captured;
9130         if (p >= (int) BlackPawn) {
9131           p -= (int)BlackPawn;
9132           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9133                   /* in Shogi restore piece to its original  first */
9134                   captured = (ChessSquare) (DEMOTED captured);
9135                   p = DEMOTED p;
9136           }
9137           p = PieceToNumber((ChessSquare)p);
9138           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9139           board[p][BOARD_WIDTH-2]++;
9140           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9141         } else {
9142           p -= (int)WhitePawn;
9143           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9144                   captured = (ChessSquare) (DEMOTED captured);
9145                   p = DEMOTED p;
9146           }
9147           p = PieceToNumber((ChessSquare)p);
9148           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9149           board[BOARD_HEIGHT-1-p][1]++;
9150           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9151         }
9152       }
9153     } else if (gameInfo.variant == VariantAtomic) {
9154       if (captured != EmptySquare) {
9155         int y, x;
9156         for (y = toY-1; y <= toY+1; y++) {
9157           for (x = toX-1; x <= toX+1; x++) {
9158             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9159                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9160               board[y][x] = EmptySquare;
9161             }
9162           }
9163         }
9164         board[toY][toX] = EmptySquare;
9165       }
9166     }
9167     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9168         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9169     } else
9170     if(promoChar == '+') {
9171         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9172         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9173     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9174         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9175     }
9176     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9177                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9178         // [HGM] superchess: take promotion piece out of holdings
9179         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9180         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9181             if(!--board[k][BOARD_WIDTH-2])
9182                 board[k][BOARD_WIDTH-1] = EmptySquare;
9183         } else {
9184             if(!--board[BOARD_HEIGHT-1-k][1])
9185                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9186         }
9187     }
9188
9189 }
9190
9191 /* Updates forwardMostMove */
9192 void
9193 MakeMove(fromX, fromY, toX, toY, promoChar)
9194      int fromX, fromY, toX, toY;
9195      int promoChar;
9196 {
9197 //    forwardMostMove++; // [HGM] bare: moved downstream
9198
9199     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9200         int timeLeft; static int lastLoadFlag=0; int king, piece;
9201         piece = boards[forwardMostMove][fromY][fromX];
9202         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9203         if(gameInfo.variant == VariantKnightmate)
9204             king += (int) WhiteUnicorn - (int) WhiteKing;
9205         if(forwardMostMove == 0) {
9206             if(blackPlaysFirst)
9207                 fprintf(serverMoves, "%s;", second.tidy);
9208             fprintf(serverMoves, "%s;", first.tidy);
9209             if(!blackPlaysFirst)
9210                 fprintf(serverMoves, "%s;", second.tidy);
9211         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9212         lastLoadFlag = loadFlag;
9213         // print base move
9214         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9215         // print castling suffix
9216         if( toY == fromY && piece == king ) {
9217             if(toX-fromX > 1)
9218                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9219             if(fromX-toX >1)
9220                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9221         }
9222         // e.p. suffix
9223         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9224              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9225              boards[forwardMostMove][toY][toX] == EmptySquare
9226              && fromX != toX && fromY != toY)
9227                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9228         // promotion suffix
9229         if(promoChar != NULLCHAR)
9230                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9231         if(!loadFlag) {
9232             fprintf(serverMoves, "/%d/%d",
9233                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9234             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9235             else                      timeLeft = blackTimeRemaining/1000;
9236             fprintf(serverMoves, "/%d", timeLeft);
9237         }
9238         fflush(serverMoves);
9239     }
9240
9241     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9242       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9243                         0, 1);
9244       return;
9245     }
9246     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9247     if (commentList[forwardMostMove+1] != NULL) {
9248         free(commentList[forwardMostMove+1]);
9249         commentList[forwardMostMove+1] = NULL;
9250     }
9251     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9252     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9253     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9254     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9255     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9256     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9257     gameInfo.result = GameUnfinished;
9258     if (gameInfo.resultDetails != NULL) {
9259         free(gameInfo.resultDetails);
9260         gameInfo.resultDetails = NULL;
9261     }
9262     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9263                               moveList[forwardMostMove - 1]);
9264     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9265                              PosFlags(forwardMostMove - 1),
9266                              fromY, fromX, toY, toX, promoChar,
9267                              parseList[forwardMostMove - 1]);
9268     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9269       case MT_NONE:
9270       case MT_STALEMATE:
9271       default:
9272         break;
9273       case MT_CHECK:
9274         if(gameInfo.variant != VariantShogi)
9275             strcat(parseList[forwardMostMove - 1], "+");
9276         break;
9277       case MT_CHECKMATE:
9278       case MT_STAINMATE:
9279         strcat(parseList[forwardMostMove - 1], "#");
9280         break;
9281     }
9282     if (appData.debugMode) {
9283         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9284     }
9285
9286 }
9287
9288 /* Updates currentMove if not pausing */
9289 void
9290 ShowMove(fromX, fromY, toX, toY)
9291 {
9292     int instant = (gameMode == PlayFromGameFile) ?
9293         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9294     if(appData.noGUI) return;
9295     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9296         if (!instant) {
9297             if (forwardMostMove == currentMove + 1) {
9298                 AnimateMove(boards[forwardMostMove - 1],
9299                             fromX, fromY, toX, toY);
9300             }
9301             if (appData.highlightLastMove) {
9302                 SetHighlights(fromX, fromY, toX, toY);
9303             }
9304         }
9305         currentMove = forwardMostMove;
9306     }
9307
9308     if (instant) return;
9309
9310     DisplayMove(currentMove - 1);
9311     DrawPosition(FALSE, boards[currentMove]);
9312     DisplayBothClocks();
9313     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9314     DisplayBook(currentMove);
9315 }
9316
9317 void SendEgtPath(ChessProgramState *cps)
9318 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9319         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9320
9321         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9322
9323         while(*p) {
9324             char c, *q = name+1, *r, *s;
9325
9326             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9327             while(*p && *p != ',') *q++ = *p++;
9328             *q++ = ':'; *q = 0;
9329             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9330                 strcmp(name, ",nalimov:") == 0 ) {
9331                 // take nalimov path from the menu-changeable option first, if it is defined
9332               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9333                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9334             } else
9335             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9336                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9337                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9338                 s = r = StrStr(s, ":") + 1; // beginning of path info
9339                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9340                 c = *r; *r = 0;             // temporarily null-terminate path info
9341                     *--q = 0;               // strip of trailig ':' from name
9342                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9343                 *r = c;
9344                 SendToProgram(buf,cps);     // send egtbpath command for this format
9345             }
9346             if(*p == ',') p++; // read away comma to position for next format name
9347         }
9348 }
9349
9350 void
9351 InitChessProgram(cps, setup)
9352      ChessProgramState *cps;
9353      int setup; /* [HGM] needed to setup FRC opening position */
9354 {
9355     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9356     if (appData.noChessProgram) return;
9357     hintRequested = FALSE;
9358     bookRequested = FALSE;
9359
9360     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9361     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9362     if(cps->memSize) { /* [HGM] memory */
9363       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9364         SendToProgram(buf, cps);
9365     }
9366     SendEgtPath(cps); /* [HGM] EGT */
9367     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9368       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9369         SendToProgram(buf, cps);
9370     }
9371
9372     SendToProgram(cps->initString, cps);
9373     if (gameInfo.variant != VariantNormal &&
9374         gameInfo.variant != VariantLoadable
9375         /* [HGM] also send variant if board size non-standard */
9376         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9377                                             ) {
9378       char *v = VariantName(gameInfo.variant);
9379       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9380         /* [HGM] in protocol 1 we have to assume all variants valid */
9381         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9382         DisplayFatalError(buf, 0, 1);
9383         return;
9384       }
9385
9386       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9387       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9388       if( gameInfo.variant == VariantXiangqi )
9389            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9390       if( gameInfo.variant == VariantShogi )
9391            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9392       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9393            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9394       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9395           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9396            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9397       if( gameInfo.variant == VariantCourier )
9398            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9399       if( gameInfo.variant == VariantSuper )
9400            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9401       if( gameInfo.variant == VariantGreat )
9402            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9403       if( gameInfo.variant == VariantSChess )
9404            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9405
9406       if(overruled) {
9407         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9408                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9409            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9410            if(StrStr(cps->variants, b) == NULL) {
9411                // specific sized variant not known, check if general sizing allowed
9412                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9413                    if(StrStr(cps->variants, "boardsize") == NULL) {
9414                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9415                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9416                        DisplayFatalError(buf, 0, 1);
9417                        return;
9418                    }
9419                    /* [HGM] here we really should compare with the maximum supported board size */
9420                }
9421            }
9422       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9423       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9424       SendToProgram(buf, cps);
9425     }
9426     currentlyInitializedVariant = gameInfo.variant;
9427
9428     /* [HGM] send opening position in FRC to first engine */
9429     if(setup) {
9430           SendToProgram("force\n", cps);
9431           SendBoard(cps, 0);
9432           /* engine is now in force mode! Set flag to wake it up after first move. */
9433           setboardSpoiledMachineBlack = 1;
9434     }
9435
9436     if (cps->sendICS) {
9437       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9438       SendToProgram(buf, cps);
9439     }
9440     cps->maybeThinking = FALSE;
9441     cps->offeredDraw = 0;
9442     if (!appData.icsActive) {
9443         SendTimeControl(cps, movesPerSession, timeControl,
9444                         timeIncrement, appData.searchDepth,
9445                         searchTime);
9446     }
9447     if (appData.showThinking
9448         // [HGM] thinking: four options require thinking output to be sent
9449         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9450                                 ) {
9451         SendToProgram("post\n", cps);
9452     }
9453     SendToProgram("hard\n", cps);
9454     if (!appData.ponderNextMove) {
9455         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9456            it without being sure what state we are in first.  "hard"
9457            is not a toggle, so that one is OK.
9458          */
9459         SendToProgram("easy\n", cps);
9460     }
9461     if (cps->usePing) {
9462       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9463       SendToProgram(buf, cps);
9464     }
9465     cps->initDone = TRUE;
9466 }
9467
9468
9469 void
9470 StartChessProgram(cps)
9471      ChessProgramState *cps;
9472 {
9473     char buf[MSG_SIZ];
9474     int err;
9475
9476     if (appData.noChessProgram) return;
9477     cps->initDone = FALSE;
9478
9479     if (strcmp(cps->host, "localhost") == 0) {
9480         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9481     } else if (*appData.remoteShell == NULLCHAR) {
9482         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9483     } else {
9484         if (*appData.remoteUser == NULLCHAR) {
9485           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9486                     cps->program);
9487         } else {
9488           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9489                     cps->host, appData.remoteUser, cps->program);
9490         }
9491         err = StartChildProcess(buf, "", &cps->pr);
9492     }
9493
9494     if (err != 0) {
9495       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9496         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9497         if(cps != &first) return;
9498         appData.noChessProgram = TRUE;
9499         ThawUI();
9500         SetNCPMode();
9501 //      DisplayFatalError(buf, err, 1);
9502 //      cps->pr = NoProc;
9503 //      cps->isr = NULL;
9504         return;
9505     }
9506
9507     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9508     if (cps->protocolVersion > 1) {
9509       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9510       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9511       cps->comboCnt = 0;  //                and values of combo boxes
9512       SendToProgram(buf, cps);
9513     } else {
9514       SendToProgram("xboard\n", cps);
9515     }
9516 }
9517
9518 void
9519 TwoMachinesEventIfReady P((void))
9520 {
9521   static int curMess = 0;
9522   if (first.lastPing != first.lastPong) {
9523     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9524     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9525     return;
9526   }
9527   if (second.lastPing != second.lastPong) {
9528     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9529     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9530     return;
9531   }
9532   DisplayMessage("", ""); curMess = 0;
9533   ThawUI();
9534   TwoMachinesEvent();
9535 }
9536
9537 int
9538 CountPlayers(char *p)
9539 {
9540     int n = 0;
9541     while(p = strchr(p, '\n')) p++, n++; // count participants
9542     return n;
9543 }
9544
9545 FILE *
9546 WriteTourneyFile(char *results)
9547 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9548     FILE *f = fopen(appData.tourneyFile, "w");
9549     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9550         // create a file with tournament description
9551         fprintf(f, "-participants {%s}\n", appData.participants);
9552         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9553         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9554         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9555         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9556         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9557         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9558         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9559         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9560         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9561         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9562         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9563         if(searchTime > 0)
9564                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9565         else {
9566                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9567                 fprintf(f, "-tc %s\n", appData.timeControl);
9568                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9569         }
9570         fprintf(f, "-results \"%s\"\n", results);
9571     }
9572     return f;
9573 }
9574
9575 int
9576 CreateTourney(char *name)
9577 {
9578         FILE *f;
9579         if(name[0] == NULLCHAR) {
9580             DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9581             return 0;
9582         }
9583         f = fopen(appData.tourneyFile, "r");
9584         if(f) { // file exists
9585             ParseArgsFromFile(f); // parse it
9586         } else {
9587             if(CountPlayers(appData.participants) < appData.tourneyType + (!appData.tourneyType) + 1) {
9588                 DisplayError(_("Not enough participants"), 0);
9589                 return 0;
9590             }
9591             if((f = WriteTourneyFile("")) == NULL) return 0;
9592         }
9593         fclose(f);
9594         appData.noChessProgram = FALSE;
9595         appData.clockMode = TRUE;
9596         SetGNUMode();
9597         return 1;
9598 }
9599
9600 #define MAXENGINES 1000
9601 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9602
9603 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9604 {
9605     char buf[MSG_SIZ], *p, *q;
9606     int i=1;
9607     while(*names) {
9608         p = names; q = buf;
9609         while(*p && *p != '\n') *q++ = *p++;
9610         *q = 0;
9611         if(engineList[i]) free(engineList[i]);
9612         engineList[i] = strdup(buf);
9613         if(*p == '\n') p++;
9614         TidyProgramName(engineList[i], "localhost", buf);
9615         if(engineMnemonic[i]) free(engineMnemonic[i]);
9616         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9617             strcat(buf, " (");
9618             sscanf(q + 8, "%s", buf + strlen(buf));
9619             strcat(buf, ")");
9620         }
9621         engineMnemonic[i] = strdup(buf);
9622         names = p; i++;
9623       if(i > MAXENGINES - 2) break;
9624     }
9625     engineList[i] = NULL;
9626 }
9627
9628 // following implemented as macro to avoid type limitations
9629 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9630
9631 void SwapEngines(int n)
9632 {   // swap settings for first engine and other engine (so far only some selected options)
9633     int h;
9634     char *p;
9635     if(n == 0) return;
9636     SWAP(directory, p)
9637     SWAP(chessProgram, p)
9638     SWAP(isUCI, h)
9639     SWAP(hasOwnBookUCI, h)
9640     SWAP(protocolVersion, h)
9641     SWAP(reuse, h)
9642     SWAP(scoreIsAbsolute, h)
9643     SWAP(timeOdds, h)
9644     SWAP(logo, p)
9645     SWAP(pgnName, p)
9646 }
9647
9648 void
9649 SetPlayer(int player)
9650 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9651     int i;
9652     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9653     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9654     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9655     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9656     if(mnemonic[i]) {
9657         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9658         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9659         ParseArgsFromString(buf);
9660     }
9661     free(engineName);
9662 }
9663
9664 int
9665 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9666 {   // determine players from game number
9667     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9668
9669     if(appData.tourneyType == 0) {
9670         roundsPerCycle = (nPlayers - 1) | 1;
9671         pairingsPerRound = nPlayers / 2;
9672     } else if(appData.tourneyType > 0) {
9673         roundsPerCycle = nPlayers - appData.tourneyType;
9674         pairingsPerRound = appData.tourneyType;
9675     }
9676     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9677     gamesPerCycle = gamesPerRound * roundsPerCycle;
9678     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9679     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9680     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9681     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9682     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9683     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9684
9685     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9686     if(appData.roundSync) *syncInterval = gamesPerRound;
9687
9688     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9689
9690     if(appData.tourneyType == 0) {
9691         if(curPairing == (nPlayers-1)/2 ) {
9692             *whitePlayer = curRound;
9693             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9694         } else {
9695             *whitePlayer = curRound - pairingsPerRound + curPairing;
9696             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9697             *blackPlayer = curRound + pairingsPerRound - curPairing;
9698             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9699         }
9700     } else if(appData.tourneyType > 0) {
9701         *whitePlayer = curPairing;
9702         *blackPlayer = curRound + appData.tourneyType;
9703     }
9704
9705     // take care of white/black alternation per round. 
9706     // For cycles and games this is already taken care of by default, derived from matchGame!
9707     return curRound & 1;
9708 }
9709
9710 int
9711 NextTourneyGame(int nr, int *swapColors)
9712 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9713     char *p, *q;
9714     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9715     FILE *tf;
9716     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9717     tf = fopen(appData.tourneyFile, "r");
9718     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9719     ParseArgsFromFile(tf); fclose(tf);
9720     InitTimeControls(); // TC might be altered from tourney file
9721
9722     p = appData.participants;
9723     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9724     *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9725
9726     if(syncInterval) {
9727         p = q = appData.results;
9728         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9729         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9730             DisplayMessage(_("Waiting for other game(s)"),"");
9731             waitingForGame = TRUE;
9732             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9733             return 0;
9734         }
9735         waitingForGame = FALSE;
9736     }
9737
9738     if(first.pr != NoProc) return 1; // engines already loaded
9739
9740     // redefine engines, engine dir, etc.
9741     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9742     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9743     SwapEngines(1);
9744     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9745     SwapEngines(1);         // and make that valid for second engine by swapping
9746     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9747     InitEngine(&second, 1);
9748     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9749     return 1;
9750 }
9751
9752 void
9753 NextMatchGame()
9754 {   // performs game initialization that does not invoke engines, and then tries to start the game
9755     int firstWhite, swapColors = 0;
9756     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9757     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9758     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9759     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9760     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9761     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9762     Reset(FALSE, first.pr != NoProc);
9763     appData.noChessProgram = FALSE;
9764     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9765     TwoMachinesEvent();
9766 }
9767
9768 void UserAdjudicationEvent( int result )
9769 {
9770     ChessMove gameResult = GameIsDrawn;
9771
9772     if( result > 0 ) {
9773         gameResult = WhiteWins;
9774     }
9775     else if( result < 0 ) {
9776         gameResult = BlackWins;
9777     }
9778
9779     if( gameMode == TwoMachinesPlay ) {
9780         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9781     }
9782 }
9783
9784
9785 // [HGM] save: calculate checksum of game to make games easily identifiable
9786 int StringCheckSum(char *s)
9787 {
9788         int i = 0;
9789         if(s==NULL) return 0;
9790         while(*s) i = i*259 + *s++;
9791         return i;
9792 }
9793
9794 int GameCheckSum()
9795 {
9796         int i, sum=0;
9797         for(i=backwardMostMove; i<forwardMostMove; i++) {
9798                 sum += pvInfoList[i].depth;
9799                 sum += StringCheckSum(parseList[i]);
9800                 sum += StringCheckSum(commentList[i]);
9801                 sum *= 261;
9802         }
9803         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9804         return sum + StringCheckSum(commentList[i]);
9805 } // end of save patch
9806
9807 void
9808 GameEnds(result, resultDetails, whosays)
9809      ChessMove result;
9810      char *resultDetails;
9811      int whosays;
9812 {
9813     GameMode nextGameMode;
9814     int isIcsGame;
9815     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9816
9817     if(endingGame) return; /* [HGM] crash: forbid recursion */
9818     endingGame = 1;
9819     if(twoBoards) { // [HGM] dual: switch back to one board
9820         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9821         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9822     }
9823     if (appData.debugMode) {
9824       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9825               result, resultDetails ? resultDetails : "(null)", whosays);
9826     }
9827
9828     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9829
9830     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9831         /* If we are playing on ICS, the server decides when the
9832            game is over, but the engine can offer to draw, claim
9833            a draw, or resign.
9834          */
9835 #if ZIPPY
9836         if (appData.zippyPlay && first.initDone) {
9837             if (result == GameIsDrawn) {
9838                 /* In case draw still needs to be claimed */
9839                 SendToICS(ics_prefix);
9840                 SendToICS("draw\n");
9841             } else if (StrCaseStr(resultDetails, "resign")) {
9842                 SendToICS(ics_prefix);
9843                 SendToICS("resign\n");
9844             }
9845         }
9846 #endif
9847         endingGame = 0; /* [HGM] crash */
9848         return;
9849     }
9850
9851     /* If we're loading the game from a file, stop */
9852     if (whosays == GE_FILE) {
9853       (void) StopLoadGameTimer();
9854       gameFileFP = NULL;
9855     }
9856
9857     /* Cancel draw offers */
9858     first.offeredDraw = second.offeredDraw = 0;
9859
9860     /* If this is an ICS game, only ICS can really say it's done;
9861        if not, anyone can. */
9862     isIcsGame = (gameMode == IcsPlayingWhite ||
9863                  gameMode == IcsPlayingBlack ||
9864                  gameMode == IcsObserving    ||
9865                  gameMode == IcsExamining);
9866
9867     if (!isIcsGame || whosays == GE_ICS) {
9868         /* OK -- not an ICS game, or ICS said it was done */
9869         StopClocks();
9870         if (!isIcsGame && !appData.noChessProgram)
9871           SetUserThinkingEnables();
9872
9873         /* [HGM] if a machine claims the game end we verify this claim */
9874         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9875             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9876                 char claimer;
9877                 ChessMove trueResult = (ChessMove) -1;
9878
9879                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9880                                             first.twoMachinesColor[0] :
9881                                             second.twoMachinesColor[0] ;
9882
9883                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9884                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9885                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9886                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9887                 } else
9888                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9889                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9890                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9891                 } else
9892                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9893                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9894                 }
9895
9896                 // now verify win claims, but not in drop games, as we don't understand those yet
9897                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9898                                                  || gameInfo.variant == VariantGreat) &&
9899                     (result == WhiteWins && claimer == 'w' ||
9900                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9901                       if (appData.debugMode) {
9902                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9903                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9904                       }
9905                       if(result != trueResult) {
9906                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9907                               result = claimer == 'w' ? BlackWins : WhiteWins;
9908                               resultDetails = buf;
9909                       }
9910                 } else
9911                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9912                     && (forwardMostMove <= backwardMostMove ||
9913                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9914                         (claimer=='b')==(forwardMostMove&1))
9915                                                                                   ) {
9916                       /* [HGM] verify: draws that were not flagged are false claims */
9917                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9918                       result = claimer == 'w' ? BlackWins : WhiteWins;
9919                       resultDetails = buf;
9920                 }
9921                 /* (Claiming a loss is accepted no questions asked!) */
9922             }
9923             /* [HGM] bare: don't allow bare King to win */
9924             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9925                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9926                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9927                && result != GameIsDrawn)
9928             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9929                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9930                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9931                         if(p >= 0 && p <= (int)WhiteKing) k++;
9932                 }
9933                 if (appData.debugMode) {
9934                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9935                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9936                 }
9937                 if(k <= 1) {
9938                         result = GameIsDrawn;
9939                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9940                         resultDetails = buf;
9941                 }
9942             }
9943         }
9944
9945
9946         if(serverMoves != NULL && !loadFlag) { char c = '=';
9947             if(result==WhiteWins) c = '+';
9948             if(result==BlackWins) c = '-';
9949             if(resultDetails != NULL)
9950                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9951         }
9952         if (resultDetails != NULL) {
9953             gameInfo.result = result;
9954             gameInfo.resultDetails = StrSave(resultDetails);
9955
9956             /* display last move only if game was not loaded from file */
9957             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9958                 DisplayMove(currentMove - 1);
9959
9960             if (forwardMostMove != 0) {
9961                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9962                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9963                                                                 ) {
9964                     if (*appData.saveGameFile != NULLCHAR) {
9965                         SaveGameToFile(appData.saveGameFile, TRUE);
9966                     } else if (appData.autoSaveGames) {
9967                         AutoSaveGame();
9968                     }
9969                     if (*appData.savePositionFile != NULLCHAR) {
9970                         SavePositionToFile(appData.savePositionFile);
9971                     }
9972                 }
9973             }
9974
9975             /* Tell program how game ended in case it is learning */
9976             /* [HGM] Moved this to after saving the PGN, just in case */
9977             /* engine died and we got here through time loss. In that */
9978             /* case we will get a fatal error writing the pipe, which */
9979             /* would otherwise lose us the PGN.                       */
9980             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9981             /* output during GameEnds should never be fatal anymore   */
9982             if (gameMode == MachinePlaysWhite ||
9983                 gameMode == MachinePlaysBlack ||
9984                 gameMode == TwoMachinesPlay ||
9985                 gameMode == IcsPlayingWhite ||
9986                 gameMode == IcsPlayingBlack ||
9987                 gameMode == BeginningOfGame) {
9988                 char buf[MSG_SIZ];
9989                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9990                         resultDetails);
9991                 if (first.pr != NoProc) {
9992                     SendToProgram(buf, &first);
9993                 }
9994                 if (second.pr != NoProc &&
9995                     gameMode == TwoMachinesPlay) {
9996                     SendToProgram(buf, &second);
9997                 }
9998             }
9999         }
10000
10001         if (appData.icsActive) {
10002             if (appData.quietPlay &&
10003                 (gameMode == IcsPlayingWhite ||
10004                  gameMode == IcsPlayingBlack)) {
10005                 SendToICS(ics_prefix);
10006                 SendToICS("set shout 1\n");
10007             }
10008             nextGameMode = IcsIdle;
10009             ics_user_moved = FALSE;
10010             /* clean up premove.  It's ugly when the game has ended and the
10011              * premove highlights are still on the board.
10012              */
10013             if (gotPremove) {
10014               gotPremove = FALSE;
10015               ClearPremoveHighlights();
10016               DrawPosition(FALSE, boards[currentMove]);
10017             }
10018             if (whosays == GE_ICS) {
10019                 switch (result) {
10020                 case WhiteWins:
10021                     if (gameMode == IcsPlayingWhite)
10022                         PlayIcsWinSound();
10023                     else if(gameMode == IcsPlayingBlack)
10024                         PlayIcsLossSound();
10025                     break;
10026                 case BlackWins:
10027                     if (gameMode == IcsPlayingBlack)
10028                         PlayIcsWinSound();
10029                     else if(gameMode == IcsPlayingWhite)
10030                         PlayIcsLossSound();
10031                     break;
10032                 case GameIsDrawn:
10033                     PlayIcsDrawSound();
10034                     break;
10035                 default:
10036                     PlayIcsUnfinishedSound();
10037                 }
10038             }
10039         } else if (gameMode == EditGame ||
10040                    gameMode == PlayFromGameFile ||
10041                    gameMode == AnalyzeMode ||
10042                    gameMode == AnalyzeFile) {
10043             nextGameMode = gameMode;
10044         } else {
10045             nextGameMode = EndOfGame;
10046         }
10047         pausing = FALSE;
10048         ModeHighlight();
10049     } else {
10050         nextGameMode = gameMode;
10051     }
10052
10053     if (appData.noChessProgram) {
10054         gameMode = nextGameMode;
10055         ModeHighlight();
10056         endingGame = 0; /* [HGM] crash */
10057         return;
10058     }
10059
10060     if (first.reuse) {
10061         /* Put first chess program into idle state */
10062         if (first.pr != NoProc &&
10063             (gameMode == MachinePlaysWhite ||
10064              gameMode == MachinePlaysBlack ||
10065              gameMode == TwoMachinesPlay ||
10066              gameMode == IcsPlayingWhite ||
10067              gameMode == IcsPlayingBlack ||
10068              gameMode == BeginningOfGame)) {
10069             SendToProgram("force\n", &first);
10070             if (first.usePing) {
10071               char buf[MSG_SIZ];
10072               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10073               SendToProgram(buf, &first);
10074             }
10075         }
10076     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10077         /* Kill off first chess program */
10078         if (first.isr != NULL)
10079           RemoveInputSource(first.isr);
10080         first.isr = NULL;
10081
10082         if (first.pr != NoProc) {
10083             ExitAnalyzeMode();
10084             DoSleep( appData.delayBeforeQuit );
10085             SendToProgram("quit\n", &first);
10086             DoSleep( appData.delayAfterQuit );
10087             DestroyChildProcess(first.pr, first.useSigterm);
10088         }
10089         first.pr = NoProc;
10090     }
10091     if (second.reuse) {
10092         /* Put second chess program into idle state */
10093         if (second.pr != NoProc &&
10094             gameMode == TwoMachinesPlay) {
10095             SendToProgram("force\n", &second);
10096             if (second.usePing) {
10097               char buf[MSG_SIZ];
10098               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10099               SendToProgram(buf, &second);
10100             }
10101         }
10102     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10103         /* Kill off second chess program */
10104         if (second.isr != NULL)
10105           RemoveInputSource(second.isr);
10106         second.isr = NULL;
10107
10108         if (second.pr != NoProc) {
10109             DoSleep( appData.delayBeforeQuit );
10110             SendToProgram("quit\n", &second);
10111             DoSleep( appData.delayAfterQuit );
10112             DestroyChildProcess(second.pr, second.useSigterm);
10113         }
10114         second.pr = NoProc;
10115     }
10116
10117     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10118         char resChar = '=';
10119         switch (result) {
10120         case WhiteWins:
10121           resChar = '+';
10122           if (first.twoMachinesColor[0] == 'w') {
10123             first.matchWins++;
10124           } else {
10125             second.matchWins++;
10126           }
10127           break;
10128         case BlackWins:
10129           resChar = '-';
10130           if (first.twoMachinesColor[0] == 'b') {
10131             first.matchWins++;
10132           } else {
10133             second.matchWins++;
10134           }
10135           break;
10136         case GameUnfinished:
10137           resChar = ' ';
10138         default:
10139           break;
10140         }
10141
10142         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10143         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10144             ReserveGame(nextGame, resChar); // sets nextGame
10145             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10146         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10147
10148         if (nextGame <= appData.matchGames && !abortMatch) {
10149             gameMode = nextGameMode;
10150             matchGame = nextGame; // this will be overruled in tourney mode!
10151             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10152             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10153             endingGame = 0; /* [HGM] crash */
10154             return;
10155         } else {
10156             gameMode = nextGameMode;
10157             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10158                      first.tidy, second.tidy,
10159                      first.matchWins, second.matchWins,
10160                      appData.matchGames - (first.matchWins + second.matchWins));
10161             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10162             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10163                 first.twoMachinesColor = "black\n";
10164                 second.twoMachinesColor = "white\n";
10165             } else {
10166                 first.twoMachinesColor = "white\n";
10167                 second.twoMachinesColor = "black\n";
10168             }
10169         }
10170     }
10171     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10172         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10173       ExitAnalyzeMode();
10174     gameMode = nextGameMode;
10175     ModeHighlight();
10176     endingGame = 0;  /* [HGM] crash */
10177     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10178         if(matchMode == TRUE) { // match through command line: exit with or without popup
10179             if(ranking) {
10180                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10181                 else ExitEvent(0);
10182             } else DisplayFatalError(buf, 0, 0);
10183         } else { // match through menu; just stop, with or without popup
10184             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10185             if(ranking){
10186                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10187             } else DisplayNote(buf);
10188       }
10189       if(ranking) free(ranking);
10190     }
10191 }
10192
10193 /* Assumes program was just initialized (initString sent).
10194    Leaves program in force mode. */
10195 void
10196 FeedMovesToProgram(cps, upto)
10197      ChessProgramState *cps;
10198      int upto;
10199 {
10200     int i;
10201
10202     if (appData.debugMode)
10203       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10204               startedFromSetupPosition ? "position and " : "",
10205               backwardMostMove, upto, cps->which);
10206     if(currentlyInitializedVariant != gameInfo.variant) {
10207       char buf[MSG_SIZ];
10208         // [HGM] variantswitch: make engine aware of new variant
10209         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10210                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10211         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10212         SendToProgram(buf, cps);
10213         currentlyInitializedVariant = gameInfo.variant;
10214     }
10215     SendToProgram("force\n", cps);
10216     if (startedFromSetupPosition) {
10217         SendBoard(cps, backwardMostMove);
10218     if (appData.debugMode) {
10219         fprintf(debugFP, "feedMoves\n");
10220     }
10221     }
10222     for (i = backwardMostMove; i < upto; i++) {
10223         SendMoveToProgram(i, cps);
10224     }
10225 }
10226
10227
10228 int
10229 ResurrectChessProgram()
10230 {
10231      /* The chess program may have exited.
10232         If so, restart it and feed it all the moves made so far. */
10233     static int doInit = 0;
10234
10235     if (appData.noChessProgram) return 1;
10236
10237     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10238         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10239         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10240         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10241     } else {
10242         if (first.pr != NoProc) return 1;
10243         StartChessProgram(&first);
10244     }
10245     InitChessProgram(&first, FALSE);
10246     FeedMovesToProgram(&first, currentMove);
10247
10248     if (!first.sendTime) {
10249         /* can't tell gnuchess what its clock should read,
10250            so we bow to its notion. */
10251         ResetClocks();
10252         timeRemaining[0][currentMove] = whiteTimeRemaining;
10253         timeRemaining[1][currentMove] = blackTimeRemaining;
10254     }
10255
10256     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10257                 appData.icsEngineAnalyze) && first.analysisSupport) {
10258       SendToProgram("analyze\n", &first);
10259       first.analyzing = TRUE;
10260     }
10261     return 1;
10262 }
10263
10264 /*
10265  * Button procedures
10266  */
10267 void
10268 Reset(redraw, init)
10269      int redraw, init;
10270 {
10271     int i;
10272
10273     if (appData.debugMode) {
10274         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10275                 redraw, init, gameMode);
10276     }
10277     CleanupTail(); // [HGM] vari: delete any stored variations
10278     pausing = pauseExamInvalid = FALSE;
10279     startedFromSetupPosition = blackPlaysFirst = FALSE;
10280     firstMove = TRUE;
10281     whiteFlag = blackFlag = FALSE;
10282     userOfferedDraw = FALSE;
10283     hintRequested = bookRequested = FALSE;
10284     first.maybeThinking = FALSE;
10285     second.maybeThinking = FALSE;
10286     first.bookSuspend = FALSE; // [HGM] book
10287     second.bookSuspend = FALSE;
10288     thinkOutput[0] = NULLCHAR;
10289     lastHint[0] = NULLCHAR;
10290     ClearGameInfo(&gameInfo);
10291     gameInfo.variant = StringToVariant(appData.variant);
10292     ics_user_moved = ics_clock_paused = FALSE;
10293     ics_getting_history = H_FALSE;
10294     ics_gamenum = -1;
10295     white_holding[0] = black_holding[0] = NULLCHAR;
10296     ClearProgramStats();
10297     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10298
10299     ResetFrontEnd();
10300     ClearHighlights();
10301     flipView = appData.flipView;
10302     ClearPremoveHighlights();
10303     gotPremove = FALSE;
10304     alarmSounded = FALSE;
10305
10306     GameEnds(EndOfFile, NULL, GE_PLAYER);
10307     if(appData.serverMovesName != NULL) {
10308         /* [HGM] prepare to make moves file for broadcasting */
10309         clock_t t = clock();
10310         if(serverMoves != NULL) fclose(serverMoves);
10311         serverMoves = fopen(appData.serverMovesName, "r");
10312         if(serverMoves != NULL) {
10313             fclose(serverMoves);
10314             /* delay 15 sec before overwriting, so all clients can see end */
10315             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10316         }
10317         serverMoves = fopen(appData.serverMovesName, "w");
10318     }
10319
10320     ExitAnalyzeMode();
10321     gameMode = BeginningOfGame;
10322     ModeHighlight();
10323     if(appData.icsActive) gameInfo.variant = VariantNormal;
10324     currentMove = forwardMostMove = backwardMostMove = 0;
10325     InitPosition(redraw);
10326     for (i = 0; i < MAX_MOVES; i++) {
10327         if (commentList[i] != NULL) {
10328             free(commentList[i]);
10329             commentList[i] = NULL;
10330         }
10331     }
10332     ResetClocks();
10333     timeRemaining[0][0] = whiteTimeRemaining;
10334     timeRemaining[1][0] = blackTimeRemaining;
10335
10336     if (first.pr == NULL) {
10337         StartChessProgram(&first);
10338     }
10339     if (init) {
10340             InitChessProgram(&first, startedFromSetupPosition);
10341     }
10342     DisplayTitle("");
10343     DisplayMessage("", "");
10344     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10345     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10346 }
10347
10348 void
10349 AutoPlayGameLoop()
10350 {
10351     for (;;) {
10352         if (!AutoPlayOneMove())
10353           return;
10354         if (matchMode || appData.timeDelay == 0)
10355           continue;
10356         if (appData.timeDelay < 0)
10357           return;
10358         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10359         break;
10360     }
10361 }
10362
10363
10364 int
10365 AutoPlayOneMove()
10366 {
10367     int fromX, fromY, toX, toY;
10368
10369     if (appData.debugMode) {
10370       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10371     }
10372
10373     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10374       return FALSE;
10375
10376     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10377       pvInfoList[currentMove].depth = programStats.depth;
10378       pvInfoList[currentMove].score = programStats.score;
10379       pvInfoList[currentMove].time  = 0;
10380       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10381     }
10382
10383     if (currentMove >= forwardMostMove) {
10384       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10385       gameMode = EditGame;
10386       ModeHighlight();
10387
10388       /* [AS] Clear current move marker at the end of a game */
10389       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10390
10391       return FALSE;
10392     }
10393
10394     toX = moveList[currentMove][2] - AAA;
10395     toY = moveList[currentMove][3] - ONE;
10396
10397     if (moveList[currentMove][1] == '@') {
10398         if (appData.highlightLastMove) {
10399             SetHighlights(-1, -1, toX, toY);
10400         }
10401     } else {
10402         fromX = moveList[currentMove][0] - AAA;
10403         fromY = moveList[currentMove][1] - ONE;
10404
10405         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10406
10407         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10408
10409         if (appData.highlightLastMove) {
10410             SetHighlights(fromX, fromY, toX, toY);
10411         }
10412     }
10413     DisplayMove(currentMove);
10414     SendMoveToProgram(currentMove++, &first);
10415     DisplayBothClocks();
10416     DrawPosition(FALSE, boards[currentMove]);
10417     // [HGM] PV info: always display, routine tests if empty
10418     DisplayComment(currentMove - 1, commentList[currentMove]);
10419     return TRUE;
10420 }
10421
10422
10423 int
10424 LoadGameOneMove(readAhead)
10425      ChessMove readAhead;
10426 {
10427     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10428     char promoChar = NULLCHAR;
10429     ChessMove moveType;
10430     char move[MSG_SIZ];
10431     char *p, *q;
10432
10433     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10434         gameMode != AnalyzeMode && gameMode != Training) {
10435         gameFileFP = NULL;
10436         return FALSE;
10437     }
10438
10439     yyboardindex = forwardMostMove;
10440     if (readAhead != EndOfFile) {
10441       moveType = readAhead;
10442     } else {
10443       if (gameFileFP == NULL)
10444           return FALSE;
10445       moveType = (ChessMove) Myylex();
10446     }
10447
10448     done = FALSE;
10449     switch (moveType) {
10450       case Comment:
10451         if (appData.debugMode)
10452           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10453         p = yy_text;
10454
10455         /* append the comment but don't display it */
10456         AppendComment(currentMove, p, FALSE);
10457         return TRUE;
10458
10459       case WhiteCapturesEnPassant:
10460       case BlackCapturesEnPassant:
10461       case WhitePromotion:
10462       case BlackPromotion:
10463       case WhiteNonPromotion:
10464       case BlackNonPromotion:
10465       case NormalMove:
10466       case WhiteKingSideCastle:
10467       case WhiteQueenSideCastle:
10468       case BlackKingSideCastle:
10469       case BlackQueenSideCastle:
10470       case WhiteKingSideCastleWild:
10471       case WhiteQueenSideCastleWild:
10472       case BlackKingSideCastleWild:
10473       case BlackQueenSideCastleWild:
10474       /* PUSH Fabien */
10475       case WhiteHSideCastleFR:
10476       case WhiteASideCastleFR:
10477       case BlackHSideCastleFR:
10478       case BlackASideCastleFR:
10479       /* POP Fabien */
10480         if (appData.debugMode)
10481           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10482         fromX = currentMoveString[0] - AAA;
10483         fromY = currentMoveString[1] - ONE;
10484         toX = currentMoveString[2] - AAA;
10485         toY = currentMoveString[3] - ONE;
10486         promoChar = currentMoveString[4];
10487         break;
10488
10489       case WhiteDrop:
10490       case BlackDrop:
10491         if (appData.debugMode)
10492           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10493         fromX = moveType == WhiteDrop ?
10494           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10495         (int) CharToPiece(ToLower(currentMoveString[0]));
10496         fromY = DROP_RANK;
10497         toX = currentMoveString[2] - AAA;
10498         toY = currentMoveString[3] - ONE;
10499         break;
10500
10501       case WhiteWins:
10502       case BlackWins:
10503       case GameIsDrawn:
10504       case GameUnfinished:
10505         if (appData.debugMode)
10506           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10507         p = strchr(yy_text, '{');
10508         if (p == NULL) p = strchr(yy_text, '(');
10509         if (p == NULL) {
10510             p = yy_text;
10511             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10512         } else {
10513             q = strchr(p, *p == '{' ? '}' : ')');
10514             if (q != NULL) *q = NULLCHAR;
10515             p++;
10516         }
10517         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10518         GameEnds(moveType, p, GE_FILE);
10519         done = TRUE;
10520         if (cmailMsgLoaded) {
10521             ClearHighlights();
10522             flipView = WhiteOnMove(currentMove);
10523             if (moveType == GameUnfinished) flipView = !flipView;
10524             if (appData.debugMode)
10525               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10526         }
10527         break;
10528
10529       case EndOfFile:
10530         if (appData.debugMode)
10531           fprintf(debugFP, "Parser hit end of file\n");
10532         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10533           case MT_NONE:
10534           case MT_CHECK:
10535             break;
10536           case MT_CHECKMATE:
10537           case MT_STAINMATE:
10538             if (WhiteOnMove(currentMove)) {
10539                 GameEnds(BlackWins, "Black mates", GE_FILE);
10540             } else {
10541                 GameEnds(WhiteWins, "White mates", GE_FILE);
10542             }
10543             break;
10544           case MT_STALEMATE:
10545             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10546             break;
10547         }
10548         done = TRUE;
10549         break;
10550
10551       case MoveNumberOne:
10552         if (lastLoadGameStart == GNUChessGame) {
10553             /* GNUChessGames have numbers, but they aren't move numbers */
10554             if (appData.debugMode)
10555               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10556                       yy_text, (int) moveType);
10557             return LoadGameOneMove(EndOfFile); /* tail recursion */
10558         }
10559         /* else fall thru */
10560
10561       case XBoardGame:
10562       case GNUChessGame:
10563       case PGNTag:
10564         /* Reached start of next game in file */
10565         if (appData.debugMode)
10566           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10567         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10568           case MT_NONE:
10569           case MT_CHECK:
10570             break;
10571           case MT_CHECKMATE:
10572           case MT_STAINMATE:
10573             if (WhiteOnMove(currentMove)) {
10574                 GameEnds(BlackWins, "Black mates", GE_FILE);
10575             } else {
10576                 GameEnds(WhiteWins, "White mates", GE_FILE);
10577             }
10578             break;
10579           case MT_STALEMATE:
10580             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10581             break;
10582         }
10583         done = TRUE;
10584         break;
10585
10586       case PositionDiagram:     /* should not happen; ignore */
10587       case ElapsedTime:         /* ignore */
10588       case NAG:                 /* ignore */
10589         if (appData.debugMode)
10590           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10591                   yy_text, (int) moveType);
10592         return LoadGameOneMove(EndOfFile); /* tail recursion */
10593
10594       case IllegalMove:
10595         if (appData.testLegality) {
10596             if (appData.debugMode)
10597               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10598             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10599                     (forwardMostMove / 2) + 1,
10600                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10601             DisplayError(move, 0);
10602             done = TRUE;
10603         } else {
10604             if (appData.debugMode)
10605               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10606                       yy_text, currentMoveString);
10607             fromX = currentMoveString[0] - AAA;
10608             fromY = currentMoveString[1] - ONE;
10609             toX = currentMoveString[2] - AAA;
10610             toY = currentMoveString[3] - ONE;
10611             promoChar = currentMoveString[4];
10612         }
10613         break;
10614
10615       case AmbiguousMove:
10616         if (appData.debugMode)
10617           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10618         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10619                 (forwardMostMove / 2) + 1,
10620                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10621         DisplayError(move, 0);
10622         done = TRUE;
10623         break;
10624
10625       default:
10626       case ImpossibleMove:
10627         if (appData.debugMode)
10628           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10629         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10630                 (forwardMostMove / 2) + 1,
10631                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10632         DisplayError(move, 0);
10633         done = TRUE;
10634         break;
10635     }
10636
10637     if (done) {
10638         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10639             DrawPosition(FALSE, boards[currentMove]);
10640             DisplayBothClocks();
10641             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10642               DisplayComment(currentMove - 1, commentList[currentMove]);
10643         }
10644         (void) StopLoadGameTimer();
10645         gameFileFP = NULL;
10646         cmailOldMove = forwardMostMove;
10647         return FALSE;
10648     } else {
10649         /* currentMoveString is set as a side-effect of yylex */
10650
10651         thinkOutput[0] = NULLCHAR;
10652         MakeMove(fromX, fromY, toX, toY, promoChar);
10653         currentMove = forwardMostMove;
10654         return TRUE;
10655     }
10656 }
10657
10658 /* Load the nth game from the given file */
10659 int
10660 LoadGameFromFile(filename, n, title, useList)
10661      char *filename;
10662      int n;
10663      char *title;
10664      /*Boolean*/ int useList;
10665 {
10666     FILE *f;
10667     char buf[MSG_SIZ];
10668
10669     if (strcmp(filename, "-") == 0) {
10670         f = stdin;
10671         title = "stdin";
10672     } else {
10673         f = fopen(filename, "rb");
10674         if (f == NULL) {
10675           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10676             DisplayError(buf, errno);
10677             return FALSE;
10678         }
10679     }
10680     if (fseek(f, 0, 0) == -1) {
10681         /* f is not seekable; probably a pipe */
10682         useList = FALSE;
10683     }
10684     if (useList && n == 0) {
10685         int error = GameListBuild(f);
10686         if (error) {
10687             DisplayError(_("Cannot build game list"), error);
10688         } else if (!ListEmpty(&gameList) &&
10689                    ((ListGame *) gameList.tailPred)->number > 1) {
10690             GameListPopUp(f, title);
10691             return TRUE;
10692         }
10693         GameListDestroy();
10694         n = 1;
10695     }
10696     if (n == 0) n = 1;
10697     return LoadGame(f, n, title, FALSE);
10698 }
10699
10700
10701 void
10702 MakeRegisteredMove()
10703 {
10704     int fromX, fromY, toX, toY;
10705     char promoChar;
10706     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10707         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10708           case CMAIL_MOVE:
10709           case CMAIL_DRAW:
10710             if (appData.debugMode)
10711               fprintf(debugFP, "Restoring %s for game %d\n",
10712                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10713
10714             thinkOutput[0] = NULLCHAR;
10715             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10716             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10717             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10718             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10719             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10720             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10721             MakeMove(fromX, fromY, toX, toY, promoChar);
10722             ShowMove(fromX, fromY, toX, toY);
10723
10724             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10725               case MT_NONE:
10726               case MT_CHECK:
10727                 break;
10728
10729               case MT_CHECKMATE:
10730               case MT_STAINMATE:
10731                 if (WhiteOnMove(currentMove)) {
10732                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10733                 } else {
10734                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10735                 }
10736                 break;
10737
10738               case MT_STALEMATE:
10739                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10740                 break;
10741             }
10742
10743             break;
10744
10745           case CMAIL_RESIGN:
10746             if (WhiteOnMove(currentMove)) {
10747                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10748             } else {
10749                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10750             }
10751             break;
10752
10753           case CMAIL_ACCEPT:
10754             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10755             break;
10756
10757           default:
10758             break;
10759         }
10760     }
10761
10762     return;
10763 }
10764
10765 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10766 int
10767 CmailLoadGame(f, gameNumber, title, useList)
10768      FILE *f;
10769      int gameNumber;
10770      char *title;
10771      int useList;
10772 {
10773     int retVal;
10774
10775     if (gameNumber > nCmailGames) {
10776         DisplayError(_("No more games in this message"), 0);
10777         return FALSE;
10778     }
10779     if (f == lastLoadGameFP) {
10780         int offset = gameNumber - lastLoadGameNumber;
10781         if (offset == 0) {
10782             cmailMsg[0] = NULLCHAR;
10783             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10784                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10785                 nCmailMovesRegistered--;
10786             }
10787             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10788             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10789                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10790             }
10791         } else {
10792             if (! RegisterMove()) return FALSE;
10793         }
10794     }
10795
10796     retVal = LoadGame(f, gameNumber, title, useList);
10797
10798     /* Make move registered during previous look at this game, if any */
10799     MakeRegisteredMove();
10800
10801     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10802         commentList[currentMove]
10803           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10804         DisplayComment(currentMove - 1, commentList[currentMove]);
10805     }
10806
10807     return retVal;
10808 }
10809
10810 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10811 int
10812 ReloadGame(offset)
10813      int offset;
10814 {
10815     int gameNumber = lastLoadGameNumber + offset;
10816     if (lastLoadGameFP == NULL) {
10817         DisplayError(_("No game has been loaded yet"), 0);
10818         return FALSE;
10819     }
10820     if (gameNumber <= 0) {
10821         DisplayError(_("Can't back up any further"), 0);
10822         return FALSE;
10823     }
10824     if (cmailMsgLoaded) {
10825         return CmailLoadGame(lastLoadGameFP, gameNumber,
10826                              lastLoadGameTitle, lastLoadGameUseList);
10827     } else {
10828         return LoadGame(lastLoadGameFP, gameNumber,
10829                         lastLoadGameTitle, lastLoadGameUseList);
10830     }
10831 }
10832
10833
10834
10835 /* Load the nth game from open file f */
10836 int
10837 LoadGame(f, gameNumber, title, useList)
10838      FILE *f;
10839      int gameNumber;
10840      char *title;
10841      int useList;
10842 {
10843     ChessMove cm;
10844     char buf[MSG_SIZ];
10845     int gn = gameNumber;
10846     ListGame *lg = NULL;
10847     int numPGNTags = 0;
10848     int err;
10849     GameMode oldGameMode;
10850     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10851
10852     if (appData.debugMode)
10853         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10854
10855     if (gameMode == Training )
10856         SetTrainingModeOff();
10857
10858     oldGameMode = gameMode;
10859     if (gameMode != BeginningOfGame) {
10860       Reset(FALSE, TRUE);
10861     }
10862
10863     gameFileFP = f;
10864     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10865         fclose(lastLoadGameFP);
10866     }
10867
10868     if (useList) {
10869         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10870
10871         if (lg) {
10872             fseek(f, lg->offset, 0);
10873             GameListHighlight(gameNumber);
10874             gn = 1;
10875         }
10876         else {
10877             DisplayError(_("Game number out of range"), 0);
10878             return FALSE;
10879         }
10880     } else {
10881         GameListDestroy();
10882         if (fseek(f, 0, 0) == -1) {
10883             if (f == lastLoadGameFP ?
10884                 gameNumber == lastLoadGameNumber + 1 :
10885                 gameNumber == 1) {
10886                 gn = 1;
10887             } else {
10888                 DisplayError(_("Can't seek on game file"), 0);
10889                 return FALSE;
10890             }
10891         }
10892     }
10893     lastLoadGameFP = f;
10894     lastLoadGameNumber = gameNumber;
10895     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10896     lastLoadGameUseList = useList;
10897
10898     yynewfile(f);
10899
10900     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10901       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10902                 lg->gameInfo.black);
10903             DisplayTitle(buf);
10904     } else if (*title != NULLCHAR) {
10905         if (gameNumber > 1) {
10906           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10907             DisplayTitle(buf);
10908         } else {
10909             DisplayTitle(title);
10910         }
10911     }
10912
10913     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10914         gameMode = PlayFromGameFile;
10915         ModeHighlight();
10916     }
10917
10918     currentMove = forwardMostMove = backwardMostMove = 0;
10919     CopyBoard(boards[0], initialPosition);
10920     StopClocks();
10921
10922     /*
10923      * Skip the first gn-1 games in the file.
10924      * Also skip over anything that precedes an identifiable
10925      * start of game marker, to avoid being confused by
10926      * garbage at the start of the file.  Currently
10927      * recognized start of game markers are the move number "1",
10928      * the pattern "gnuchess .* game", the pattern
10929      * "^[#;%] [^ ]* game file", and a PGN tag block.
10930      * A game that starts with one of the latter two patterns
10931      * will also have a move number 1, possibly
10932      * following a position diagram.
10933      * 5-4-02: Let's try being more lenient and allowing a game to
10934      * start with an unnumbered move.  Does that break anything?
10935      */
10936     cm = lastLoadGameStart = EndOfFile;
10937     while (gn > 0) {
10938         yyboardindex = forwardMostMove;
10939         cm = (ChessMove) Myylex();
10940         switch (cm) {
10941           case EndOfFile:
10942             if (cmailMsgLoaded) {
10943                 nCmailGames = CMAIL_MAX_GAMES - gn;
10944             } else {
10945                 Reset(TRUE, TRUE);
10946                 DisplayError(_("Game not found in file"), 0);
10947             }
10948             return FALSE;
10949
10950           case GNUChessGame:
10951           case XBoardGame:
10952             gn--;
10953             lastLoadGameStart = cm;
10954             break;
10955
10956           case MoveNumberOne:
10957             switch (lastLoadGameStart) {
10958               case GNUChessGame:
10959               case XBoardGame:
10960               case PGNTag:
10961                 break;
10962               case MoveNumberOne:
10963               case EndOfFile:
10964                 gn--;           /* count this game */
10965                 lastLoadGameStart = cm;
10966                 break;
10967               default:
10968                 /* impossible */
10969                 break;
10970             }
10971             break;
10972
10973           case PGNTag:
10974             switch (lastLoadGameStart) {
10975               case GNUChessGame:
10976               case PGNTag:
10977               case MoveNumberOne:
10978               case EndOfFile:
10979                 gn--;           /* count this game */
10980                 lastLoadGameStart = cm;
10981                 break;
10982               case XBoardGame:
10983                 lastLoadGameStart = cm; /* game counted already */
10984                 break;
10985               default:
10986                 /* impossible */
10987                 break;
10988             }
10989             if (gn > 0) {
10990                 do {
10991                     yyboardindex = forwardMostMove;
10992                     cm = (ChessMove) Myylex();
10993                 } while (cm == PGNTag || cm == Comment);
10994             }
10995             break;
10996
10997           case WhiteWins:
10998           case BlackWins:
10999           case GameIsDrawn:
11000             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11001                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11002                     != CMAIL_OLD_RESULT) {
11003                     nCmailResults ++ ;
11004                     cmailResult[  CMAIL_MAX_GAMES
11005                                 - gn - 1] = CMAIL_OLD_RESULT;
11006                 }
11007             }
11008             break;
11009
11010           case NormalMove:
11011             /* Only a NormalMove can be at the start of a game
11012              * without a position diagram. */
11013             if (lastLoadGameStart == EndOfFile ) {
11014               gn--;
11015               lastLoadGameStart = MoveNumberOne;
11016             }
11017             break;
11018
11019           default:
11020             break;
11021         }
11022     }
11023
11024     if (appData.debugMode)
11025       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11026
11027     if (cm == XBoardGame) {
11028         /* Skip any header junk before position diagram and/or move 1 */
11029         for (;;) {
11030             yyboardindex = forwardMostMove;
11031             cm = (ChessMove) Myylex();
11032
11033             if (cm == EndOfFile ||
11034                 cm == GNUChessGame || cm == XBoardGame) {
11035                 /* Empty game; pretend end-of-file and handle later */
11036                 cm = EndOfFile;
11037                 break;
11038             }
11039
11040             if (cm == MoveNumberOne || cm == PositionDiagram ||
11041                 cm == PGNTag || cm == Comment)
11042               break;
11043         }
11044     } else if (cm == GNUChessGame) {
11045         if (gameInfo.event != NULL) {
11046             free(gameInfo.event);
11047         }
11048         gameInfo.event = StrSave(yy_text);
11049     }
11050
11051     startedFromSetupPosition = FALSE;
11052     while (cm == PGNTag) {
11053         if (appData.debugMode)
11054           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11055         err = ParsePGNTag(yy_text, &gameInfo);
11056         if (!err) numPGNTags++;
11057
11058         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11059         if(gameInfo.variant != oldVariant) {
11060             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11061             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11062             InitPosition(TRUE);
11063             oldVariant = gameInfo.variant;
11064             if (appData.debugMode)
11065               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11066         }
11067
11068
11069         if (gameInfo.fen != NULL) {
11070           Board initial_position;
11071           startedFromSetupPosition = TRUE;
11072           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11073             Reset(TRUE, TRUE);
11074             DisplayError(_("Bad FEN position in file"), 0);
11075             return FALSE;
11076           }
11077           CopyBoard(boards[0], initial_position);
11078           if (blackPlaysFirst) {
11079             currentMove = forwardMostMove = backwardMostMove = 1;
11080             CopyBoard(boards[1], initial_position);
11081             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11082             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11083             timeRemaining[0][1] = whiteTimeRemaining;
11084             timeRemaining[1][1] = blackTimeRemaining;
11085             if (commentList[0] != NULL) {
11086               commentList[1] = commentList[0];
11087               commentList[0] = NULL;
11088             }
11089           } else {
11090             currentMove = forwardMostMove = backwardMostMove = 0;
11091           }
11092           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11093           {   int i;
11094               initialRulePlies = FENrulePlies;
11095               for( i=0; i< nrCastlingRights; i++ )
11096                   initialRights[i] = initial_position[CASTLING][i];
11097           }
11098           yyboardindex = forwardMostMove;
11099           free(gameInfo.fen);
11100           gameInfo.fen = NULL;
11101         }
11102
11103         yyboardindex = forwardMostMove;
11104         cm = (ChessMove) Myylex();
11105
11106         /* Handle comments interspersed among the tags */
11107         while (cm == Comment) {
11108             char *p;
11109             if (appData.debugMode)
11110               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11111             p = yy_text;
11112             AppendComment(currentMove, p, FALSE);
11113             yyboardindex = forwardMostMove;
11114             cm = (ChessMove) Myylex();
11115         }
11116     }
11117
11118     /* don't rely on existence of Event tag since if game was
11119      * pasted from clipboard the Event tag may not exist
11120      */
11121     if (numPGNTags > 0){
11122         char *tags;
11123         if (gameInfo.variant == VariantNormal) {
11124           VariantClass v = StringToVariant(gameInfo.event);
11125           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11126           if(v < VariantShogi) gameInfo.variant = v;
11127         }
11128         if (!matchMode) {
11129           if( appData.autoDisplayTags ) {
11130             tags = PGNTags(&gameInfo);
11131             TagsPopUp(tags, CmailMsg());
11132             free(tags);
11133           }
11134         }
11135     } else {
11136         /* Make something up, but don't display it now */
11137         SetGameInfo();
11138         TagsPopDown();
11139     }
11140
11141     if (cm == PositionDiagram) {
11142         int i, j;
11143         char *p;
11144         Board initial_position;
11145
11146         if (appData.debugMode)
11147           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11148
11149         if (!startedFromSetupPosition) {
11150             p = yy_text;
11151             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11152               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11153                 switch (*p) {
11154                   case '{':
11155                   case '[':
11156                   case '-':
11157                   case ' ':
11158                   case '\t':
11159                   case '\n':
11160                   case '\r':
11161                     break;
11162                   default:
11163                     initial_position[i][j++] = CharToPiece(*p);
11164                     break;
11165                 }
11166             while (*p == ' ' || *p == '\t' ||
11167                    *p == '\n' || *p == '\r') p++;
11168
11169             if (strncmp(p, "black", strlen("black"))==0)
11170               blackPlaysFirst = TRUE;
11171             else
11172               blackPlaysFirst = FALSE;
11173             startedFromSetupPosition = TRUE;
11174
11175             CopyBoard(boards[0], initial_position);
11176             if (blackPlaysFirst) {
11177                 currentMove = forwardMostMove = backwardMostMove = 1;
11178                 CopyBoard(boards[1], initial_position);
11179                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11180                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11181                 timeRemaining[0][1] = whiteTimeRemaining;
11182                 timeRemaining[1][1] = blackTimeRemaining;
11183                 if (commentList[0] != NULL) {
11184                     commentList[1] = commentList[0];
11185                     commentList[0] = NULL;
11186                 }
11187             } else {
11188                 currentMove = forwardMostMove = backwardMostMove = 0;
11189             }
11190         }
11191         yyboardindex = forwardMostMove;
11192         cm = (ChessMove) Myylex();
11193     }
11194
11195     if (first.pr == NoProc) {
11196         StartChessProgram(&first);
11197     }
11198     InitChessProgram(&first, FALSE);
11199     SendToProgram("force\n", &first);
11200     if (startedFromSetupPosition) {
11201         SendBoard(&first, forwardMostMove);
11202     if (appData.debugMode) {
11203         fprintf(debugFP, "Load Game\n");
11204     }
11205         DisplayBothClocks();
11206     }
11207
11208     /* [HGM] server: flag to write setup moves in broadcast file as one */
11209     loadFlag = appData.suppressLoadMoves;
11210
11211     while (cm == Comment) {
11212         char *p;
11213         if (appData.debugMode)
11214           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11215         p = yy_text;
11216         AppendComment(currentMove, p, FALSE);
11217         yyboardindex = forwardMostMove;
11218         cm = (ChessMove) Myylex();
11219     }
11220
11221     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11222         cm == WhiteWins || cm == BlackWins ||
11223         cm == GameIsDrawn || cm == GameUnfinished) {
11224         DisplayMessage("", _("No moves in game"));
11225         if (cmailMsgLoaded) {
11226             if (appData.debugMode)
11227               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11228             ClearHighlights();
11229             flipView = FALSE;
11230         }
11231         DrawPosition(FALSE, boards[currentMove]);
11232         DisplayBothClocks();
11233         gameMode = EditGame;
11234         ModeHighlight();
11235         gameFileFP = NULL;
11236         cmailOldMove = 0;
11237         return TRUE;
11238     }
11239
11240     // [HGM] PV info: routine tests if comment empty
11241     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11242         DisplayComment(currentMove - 1, commentList[currentMove]);
11243     }
11244     if (!matchMode && appData.timeDelay != 0)
11245       DrawPosition(FALSE, boards[currentMove]);
11246
11247     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11248       programStats.ok_to_send = 1;
11249     }
11250
11251     /* if the first token after the PGN tags is a move
11252      * and not move number 1, retrieve it from the parser
11253      */
11254     if (cm != MoveNumberOne)
11255         LoadGameOneMove(cm);
11256
11257     /* load the remaining moves from the file */
11258     while (LoadGameOneMove(EndOfFile)) {
11259       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11260       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11261     }
11262
11263     /* rewind to the start of the game */
11264     currentMove = backwardMostMove;
11265
11266     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11267
11268     if (oldGameMode == AnalyzeFile ||
11269         oldGameMode == AnalyzeMode) {
11270       AnalyzeFileEvent();
11271     }
11272
11273     if (matchMode || appData.timeDelay == 0) {
11274       ToEndEvent();
11275       gameMode = EditGame;
11276       ModeHighlight();
11277     } else if (appData.timeDelay > 0) {
11278       AutoPlayGameLoop();
11279     }
11280
11281     if (appData.debugMode)
11282         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11283
11284     loadFlag = 0; /* [HGM] true game starts */
11285     return TRUE;
11286 }
11287
11288 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11289 int
11290 ReloadPosition(offset)
11291      int offset;
11292 {
11293     int positionNumber = lastLoadPositionNumber + offset;
11294     if (lastLoadPositionFP == NULL) {
11295         DisplayError(_("No position has been loaded yet"), 0);
11296         return FALSE;
11297     }
11298     if (positionNumber <= 0) {
11299         DisplayError(_("Can't back up any further"), 0);
11300         return FALSE;
11301     }
11302     return LoadPosition(lastLoadPositionFP, positionNumber,
11303                         lastLoadPositionTitle);
11304 }
11305
11306 /* Load the nth position from the given file */
11307 int
11308 LoadPositionFromFile(filename, n, title)
11309      char *filename;
11310      int n;
11311      char *title;
11312 {
11313     FILE *f;
11314     char buf[MSG_SIZ];
11315
11316     if (strcmp(filename, "-") == 0) {
11317         return LoadPosition(stdin, n, "stdin");
11318     } else {
11319         f = fopen(filename, "rb");
11320         if (f == NULL) {
11321             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11322             DisplayError(buf, errno);
11323             return FALSE;
11324         } else {
11325             return LoadPosition(f, n, title);
11326         }
11327     }
11328 }
11329
11330 /* Load the nth position from the given open file, and close it */
11331 int
11332 LoadPosition(f, positionNumber, title)
11333      FILE *f;
11334      int positionNumber;
11335      char *title;
11336 {
11337     char *p, line[MSG_SIZ];
11338     Board initial_position;
11339     int i, j, fenMode, pn;
11340
11341     if (gameMode == Training )
11342         SetTrainingModeOff();
11343
11344     if (gameMode != BeginningOfGame) {
11345         Reset(FALSE, TRUE);
11346     }
11347     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11348         fclose(lastLoadPositionFP);
11349     }
11350     if (positionNumber == 0) positionNumber = 1;
11351     lastLoadPositionFP = f;
11352     lastLoadPositionNumber = positionNumber;
11353     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11354     if (first.pr == NoProc) {
11355       StartChessProgram(&first);
11356       InitChessProgram(&first, FALSE);
11357     }
11358     pn = positionNumber;
11359     if (positionNumber < 0) {
11360         /* Negative position number means to seek to that byte offset */
11361         if (fseek(f, -positionNumber, 0) == -1) {
11362             DisplayError(_("Can't seek on position file"), 0);
11363             return FALSE;
11364         };
11365         pn = 1;
11366     } else {
11367         if (fseek(f, 0, 0) == -1) {
11368             if (f == lastLoadPositionFP ?
11369                 positionNumber == lastLoadPositionNumber + 1 :
11370                 positionNumber == 1) {
11371                 pn = 1;
11372             } else {
11373                 DisplayError(_("Can't seek on position file"), 0);
11374                 return FALSE;
11375             }
11376         }
11377     }
11378     /* See if this file is FEN or old-style xboard */
11379     if (fgets(line, MSG_SIZ, f) == NULL) {
11380         DisplayError(_("Position not found in file"), 0);
11381         return FALSE;
11382     }
11383     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11384     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11385
11386     if (pn >= 2) {
11387         if (fenMode || line[0] == '#') pn--;
11388         while (pn > 0) {
11389             /* skip positions before number pn */
11390             if (fgets(line, MSG_SIZ, f) == NULL) {
11391                 Reset(TRUE, TRUE);
11392                 DisplayError(_("Position not found in file"), 0);
11393                 return FALSE;
11394             }
11395             if (fenMode || line[0] == '#') pn--;
11396         }
11397     }
11398
11399     if (fenMode) {
11400         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11401             DisplayError(_("Bad FEN position in file"), 0);
11402             return FALSE;
11403         }
11404     } else {
11405         (void) fgets(line, MSG_SIZ, f);
11406         (void) fgets(line, MSG_SIZ, f);
11407
11408         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11409             (void) fgets(line, MSG_SIZ, f);
11410             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11411                 if (*p == ' ')
11412                   continue;
11413                 initial_position[i][j++] = CharToPiece(*p);
11414             }
11415         }
11416
11417         blackPlaysFirst = FALSE;
11418         if (!feof(f)) {
11419             (void) fgets(line, MSG_SIZ, f);
11420             if (strncmp(line, "black", strlen("black"))==0)
11421               blackPlaysFirst = TRUE;
11422         }
11423     }
11424     startedFromSetupPosition = TRUE;
11425
11426     SendToProgram("force\n", &first);
11427     CopyBoard(boards[0], initial_position);
11428     if (blackPlaysFirst) {
11429         currentMove = forwardMostMove = backwardMostMove = 1;
11430         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11431         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11432         CopyBoard(boards[1], initial_position);
11433         DisplayMessage("", _("Black to play"));
11434     } else {
11435         currentMove = forwardMostMove = backwardMostMove = 0;
11436         DisplayMessage("", _("White to play"));
11437     }
11438     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11439     SendBoard(&first, forwardMostMove);
11440     if (appData.debugMode) {
11441 int i, j;
11442   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11443   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11444         fprintf(debugFP, "Load Position\n");
11445     }
11446
11447     if (positionNumber > 1) {
11448       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11449         DisplayTitle(line);
11450     } else {
11451         DisplayTitle(title);
11452     }
11453     gameMode = EditGame;
11454     ModeHighlight();
11455     ResetClocks();
11456     timeRemaining[0][1] = whiteTimeRemaining;
11457     timeRemaining[1][1] = blackTimeRemaining;
11458     DrawPosition(FALSE, boards[currentMove]);
11459
11460     return TRUE;
11461 }
11462
11463
11464 void
11465 CopyPlayerNameIntoFileName(dest, src)
11466      char **dest, *src;
11467 {
11468     while (*src != NULLCHAR && *src != ',') {
11469         if (*src == ' ') {
11470             *(*dest)++ = '_';
11471             src++;
11472         } else {
11473             *(*dest)++ = *src++;
11474         }
11475     }
11476 }
11477
11478 char *DefaultFileName(ext)
11479      char *ext;
11480 {
11481     static char def[MSG_SIZ];
11482     char *p;
11483
11484     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11485         p = def;
11486         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11487         *p++ = '-';
11488         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11489         *p++ = '.';
11490         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11491     } else {
11492         def[0] = NULLCHAR;
11493     }
11494     return def;
11495 }
11496
11497 /* Save the current game to the given file */
11498 int
11499 SaveGameToFile(filename, append)
11500      char *filename;
11501      int append;
11502 {
11503     FILE *f;
11504     char buf[MSG_SIZ];
11505     int result;
11506
11507     if (strcmp(filename, "-") == 0) {
11508         return SaveGame(stdout, 0, NULL);
11509     } else {
11510         f = fopen(filename, append ? "a" : "w");
11511         if (f == NULL) {
11512             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11513             DisplayError(buf, errno);
11514             return FALSE;
11515         } else {
11516             safeStrCpy(buf, lastMsg, MSG_SIZ);
11517             DisplayMessage(_("Waiting for access to save file"), "");
11518             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11519             DisplayMessage(_("Saving game"), "");
11520             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11521             result = SaveGame(f, 0, NULL);
11522             DisplayMessage(buf, "");
11523             return result;
11524         }
11525     }
11526 }
11527
11528 char *
11529 SavePart(str)
11530      char *str;
11531 {
11532     static char buf[MSG_SIZ];
11533     char *p;
11534
11535     p = strchr(str, ' ');
11536     if (p == NULL) return str;
11537     strncpy(buf, str, p - str);
11538     buf[p - str] = NULLCHAR;
11539     return buf;
11540 }
11541
11542 #define PGN_MAX_LINE 75
11543
11544 #define PGN_SIDE_WHITE  0
11545 #define PGN_SIDE_BLACK  1
11546
11547 /* [AS] */
11548 static int FindFirstMoveOutOfBook( int side )
11549 {
11550     int result = -1;
11551
11552     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11553         int index = backwardMostMove;
11554         int has_book_hit = 0;
11555
11556         if( (index % 2) != side ) {
11557             index++;
11558         }
11559
11560         while( index < forwardMostMove ) {
11561             /* Check to see if engine is in book */
11562             int depth = pvInfoList[index].depth;
11563             int score = pvInfoList[index].score;
11564             int in_book = 0;
11565
11566             if( depth <= 2 ) {
11567                 in_book = 1;
11568             }
11569             else if( score == 0 && depth == 63 ) {
11570                 in_book = 1; /* Zappa */
11571             }
11572             else if( score == 2 && depth == 99 ) {
11573                 in_book = 1; /* Abrok */
11574             }
11575
11576             has_book_hit += in_book;
11577
11578             if( ! in_book ) {
11579                 result = index;
11580
11581                 break;
11582             }
11583
11584             index += 2;
11585         }
11586     }
11587
11588     return result;
11589 }
11590
11591 /* [AS] */
11592 void GetOutOfBookInfo( char * buf )
11593 {
11594     int oob[2];
11595     int i;
11596     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11597
11598     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11599     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11600
11601     *buf = '\0';
11602
11603     if( oob[0] >= 0 || oob[1] >= 0 ) {
11604         for( i=0; i<2; i++ ) {
11605             int idx = oob[i];
11606
11607             if( idx >= 0 ) {
11608                 if( i > 0 && oob[0] >= 0 ) {
11609                     strcat( buf, "   " );
11610                 }
11611
11612                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11613                 sprintf( buf+strlen(buf), "%s%.2f",
11614                     pvInfoList[idx].score >= 0 ? "+" : "",
11615                     pvInfoList[idx].score / 100.0 );
11616             }
11617         }
11618     }
11619 }
11620
11621 /* Save game in PGN style and close the file */
11622 int
11623 SaveGamePGN(f)
11624      FILE *f;
11625 {
11626     int i, offset, linelen, newblock;
11627     time_t tm;
11628 //    char *movetext;
11629     char numtext[32];
11630     int movelen, numlen, blank;
11631     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11632
11633     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11634
11635     tm = time((time_t *) NULL);
11636
11637     PrintPGNTags(f, &gameInfo);
11638
11639     if (backwardMostMove > 0 || startedFromSetupPosition) {
11640         char *fen = PositionToFEN(backwardMostMove, NULL);
11641         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11642         fprintf(f, "\n{--------------\n");
11643         PrintPosition(f, backwardMostMove);
11644         fprintf(f, "--------------}\n");
11645         free(fen);
11646     }
11647     else {
11648         /* [AS] Out of book annotation */
11649         if( appData.saveOutOfBookInfo ) {
11650             char buf[64];
11651
11652             GetOutOfBookInfo( buf );
11653
11654             if( buf[0] != '\0' ) {
11655                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11656             }
11657         }
11658
11659         fprintf(f, "\n");
11660     }
11661
11662     i = backwardMostMove;
11663     linelen = 0;
11664     newblock = TRUE;
11665
11666     while (i < forwardMostMove) {
11667         /* Print comments preceding this move */
11668         if (commentList[i] != NULL) {
11669             if (linelen > 0) fprintf(f, "\n");
11670             fprintf(f, "%s", commentList[i]);
11671             linelen = 0;
11672             newblock = TRUE;
11673         }
11674
11675         /* Format move number */
11676         if ((i % 2) == 0)
11677           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11678         else
11679           if (newblock)
11680             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11681           else
11682             numtext[0] = NULLCHAR;
11683
11684         numlen = strlen(numtext);
11685         newblock = FALSE;
11686
11687         /* Print move number */
11688         blank = linelen > 0 && numlen > 0;
11689         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11690             fprintf(f, "\n");
11691             linelen = 0;
11692             blank = 0;
11693         }
11694         if (blank) {
11695             fprintf(f, " ");
11696             linelen++;
11697         }
11698         fprintf(f, "%s", numtext);
11699         linelen += numlen;
11700
11701         /* Get move */
11702         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11703         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11704
11705         /* Print move */
11706         blank = linelen > 0 && movelen > 0;
11707         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11708             fprintf(f, "\n");
11709             linelen = 0;
11710             blank = 0;
11711         }
11712         if (blank) {
11713             fprintf(f, " ");
11714             linelen++;
11715         }
11716         fprintf(f, "%s", move_buffer);
11717         linelen += movelen;
11718
11719         /* [AS] Add PV info if present */
11720         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11721             /* [HGM] add time */
11722             char buf[MSG_SIZ]; int seconds;
11723
11724             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11725
11726             if( seconds <= 0)
11727               buf[0] = 0;
11728             else
11729               if( seconds < 30 )
11730                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11731               else
11732                 {
11733                   seconds = (seconds + 4)/10; // round to full seconds
11734                   if( seconds < 60 )
11735                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11736                   else
11737                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11738                 }
11739
11740             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11741                       pvInfoList[i].score >= 0 ? "+" : "",
11742                       pvInfoList[i].score / 100.0,
11743                       pvInfoList[i].depth,
11744                       buf );
11745
11746             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11747
11748             /* Print score/depth */
11749             blank = linelen > 0 && movelen > 0;
11750             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11751                 fprintf(f, "\n");
11752                 linelen = 0;
11753                 blank = 0;
11754             }
11755             if (blank) {
11756                 fprintf(f, " ");
11757                 linelen++;
11758             }
11759             fprintf(f, "%s", move_buffer);
11760             linelen += movelen;
11761         }
11762
11763         i++;
11764     }
11765
11766     /* Start a new line */
11767     if (linelen > 0) fprintf(f, "\n");
11768
11769     /* Print comments after last move */
11770     if (commentList[i] != NULL) {
11771         fprintf(f, "%s\n", commentList[i]);
11772     }
11773
11774     /* Print result */
11775     if (gameInfo.resultDetails != NULL &&
11776         gameInfo.resultDetails[0] != NULLCHAR) {
11777         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11778                 PGNResult(gameInfo.result));
11779     } else {
11780         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11781     }
11782
11783     fclose(f);
11784     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11785     return TRUE;
11786 }
11787
11788 /* Save game in old style and close the file */
11789 int
11790 SaveGameOldStyle(f)
11791      FILE *f;
11792 {
11793     int i, offset;
11794     time_t tm;
11795
11796     tm = time((time_t *) NULL);
11797
11798     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11799     PrintOpponents(f);
11800
11801     if (backwardMostMove > 0 || startedFromSetupPosition) {
11802         fprintf(f, "\n[--------------\n");
11803         PrintPosition(f, backwardMostMove);
11804         fprintf(f, "--------------]\n");
11805     } else {
11806         fprintf(f, "\n");
11807     }
11808
11809     i = backwardMostMove;
11810     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11811
11812     while (i < forwardMostMove) {
11813         if (commentList[i] != NULL) {
11814             fprintf(f, "[%s]\n", commentList[i]);
11815         }
11816
11817         if ((i % 2) == 1) {
11818             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11819             i++;
11820         } else {
11821             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11822             i++;
11823             if (commentList[i] != NULL) {
11824                 fprintf(f, "\n");
11825                 continue;
11826             }
11827             if (i >= forwardMostMove) {
11828                 fprintf(f, "\n");
11829                 break;
11830             }
11831             fprintf(f, "%s\n", parseList[i]);
11832             i++;
11833         }
11834     }
11835
11836     if (commentList[i] != NULL) {
11837         fprintf(f, "[%s]\n", commentList[i]);
11838     }
11839
11840     /* This isn't really the old style, but it's close enough */
11841     if (gameInfo.resultDetails != NULL &&
11842         gameInfo.resultDetails[0] != NULLCHAR) {
11843         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11844                 gameInfo.resultDetails);
11845     } else {
11846         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11847     }
11848
11849     fclose(f);
11850     return TRUE;
11851 }
11852
11853 /* Save the current game to open file f and close the file */
11854 int
11855 SaveGame(f, dummy, dummy2)
11856      FILE *f;
11857      int dummy;
11858      char *dummy2;
11859 {
11860     if (gameMode == EditPosition) EditPositionDone(TRUE);
11861     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11862     if (appData.oldSaveStyle)
11863       return SaveGameOldStyle(f);
11864     else
11865       return SaveGamePGN(f);
11866 }
11867
11868 /* Save the current position to the given file */
11869 int
11870 SavePositionToFile(filename)
11871      char *filename;
11872 {
11873     FILE *f;
11874     char buf[MSG_SIZ];
11875
11876     if (strcmp(filename, "-") == 0) {
11877         return SavePosition(stdout, 0, NULL);
11878     } else {
11879         f = fopen(filename, "a");
11880         if (f == NULL) {
11881             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11882             DisplayError(buf, errno);
11883             return FALSE;
11884         } else {
11885             safeStrCpy(buf, lastMsg, MSG_SIZ);
11886             DisplayMessage(_("Waiting for access to save file"), "");
11887             flock(fileno(f), LOCK_EX); // [HGM] lock
11888             DisplayMessage(_("Saving position"), "");
11889             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11890             SavePosition(f, 0, NULL);
11891             DisplayMessage(buf, "");
11892             return TRUE;
11893         }
11894     }
11895 }
11896
11897 /* Save the current position to the given open file and close the file */
11898 int
11899 SavePosition(f, dummy, dummy2)
11900      FILE *f;
11901      int dummy;
11902      char *dummy2;
11903 {
11904     time_t tm;
11905     char *fen;
11906
11907     if (gameMode == EditPosition) EditPositionDone(TRUE);
11908     if (appData.oldSaveStyle) {
11909         tm = time((time_t *) NULL);
11910
11911         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11912         PrintOpponents(f);
11913         fprintf(f, "[--------------\n");
11914         PrintPosition(f, currentMove);
11915         fprintf(f, "--------------]\n");
11916     } else {
11917         fen = PositionToFEN(currentMove, NULL);
11918         fprintf(f, "%s\n", fen);
11919         free(fen);
11920     }
11921     fclose(f);
11922     return TRUE;
11923 }
11924
11925 void
11926 ReloadCmailMsgEvent(unregister)
11927      int unregister;
11928 {
11929 #if !WIN32
11930     static char *inFilename = NULL;
11931     static char *outFilename;
11932     int i;
11933     struct stat inbuf, outbuf;
11934     int status;
11935
11936     /* Any registered moves are unregistered if unregister is set, */
11937     /* i.e. invoked by the signal handler */
11938     if (unregister) {
11939         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11940             cmailMoveRegistered[i] = FALSE;
11941             if (cmailCommentList[i] != NULL) {
11942                 free(cmailCommentList[i]);
11943                 cmailCommentList[i] = NULL;
11944             }
11945         }
11946         nCmailMovesRegistered = 0;
11947     }
11948
11949     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11950         cmailResult[i] = CMAIL_NOT_RESULT;
11951     }
11952     nCmailResults = 0;
11953
11954     if (inFilename == NULL) {
11955         /* Because the filenames are static they only get malloced once  */
11956         /* and they never get freed                                      */
11957         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11958         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11959
11960         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11961         sprintf(outFilename, "%s.out", appData.cmailGameName);
11962     }
11963
11964     status = stat(outFilename, &outbuf);
11965     if (status < 0) {
11966         cmailMailedMove = FALSE;
11967     } else {
11968         status = stat(inFilename, &inbuf);
11969         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11970     }
11971
11972     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11973        counts the games, notes how each one terminated, etc.
11974
11975        It would be nice to remove this kludge and instead gather all
11976        the information while building the game list.  (And to keep it
11977        in the game list nodes instead of having a bunch of fixed-size
11978        parallel arrays.)  Note this will require getting each game's
11979        termination from the PGN tags, as the game list builder does
11980        not process the game moves.  --mann
11981        */
11982     cmailMsgLoaded = TRUE;
11983     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11984
11985     /* Load first game in the file or popup game menu */
11986     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11987
11988 #endif /* !WIN32 */
11989     return;
11990 }
11991
11992 int
11993 RegisterMove()
11994 {
11995     FILE *f;
11996     char string[MSG_SIZ];
11997
11998     if (   cmailMailedMove
11999         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12000         return TRUE;            /* Allow free viewing  */
12001     }
12002
12003     /* Unregister move to ensure that we don't leave RegisterMove        */
12004     /* with the move registered when the conditions for registering no   */
12005     /* longer hold                                                       */
12006     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12007         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12008         nCmailMovesRegistered --;
12009
12010         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12011           {
12012               free(cmailCommentList[lastLoadGameNumber - 1]);
12013               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12014           }
12015     }
12016
12017     if (cmailOldMove == -1) {
12018         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12019         return FALSE;
12020     }
12021
12022     if (currentMove > cmailOldMove + 1) {
12023         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12024         return FALSE;
12025     }
12026
12027     if (currentMove < cmailOldMove) {
12028         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12029         return FALSE;
12030     }
12031
12032     if (forwardMostMove > currentMove) {
12033         /* Silently truncate extra moves */
12034         TruncateGame();
12035     }
12036
12037     if (   (currentMove == cmailOldMove + 1)
12038         || (   (currentMove == cmailOldMove)
12039             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12040                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12041         if (gameInfo.result != GameUnfinished) {
12042             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12043         }
12044
12045         if (commentList[currentMove] != NULL) {
12046             cmailCommentList[lastLoadGameNumber - 1]
12047               = StrSave(commentList[currentMove]);
12048         }
12049         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12050
12051         if (appData.debugMode)
12052           fprintf(debugFP, "Saving %s for game %d\n",
12053                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12054
12055         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12056
12057         f = fopen(string, "w");
12058         if (appData.oldSaveStyle) {
12059             SaveGameOldStyle(f); /* also closes the file */
12060
12061             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12062             f = fopen(string, "w");
12063             SavePosition(f, 0, NULL); /* also closes the file */
12064         } else {
12065             fprintf(f, "{--------------\n");
12066             PrintPosition(f, currentMove);
12067             fprintf(f, "--------------}\n\n");
12068
12069             SaveGame(f, 0, NULL); /* also closes the file*/
12070         }
12071
12072         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12073         nCmailMovesRegistered ++;
12074     } else if (nCmailGames == 1) {
12075         DisplayError(_("You have not made a move yet"), 0);
12076         return FALSE;
12077     }
12078
12079     return TRUE;
12080 }
12081
12082 void
12083 MailMoveEvent()
12084 {
12085 #if !WIN32
12086     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12087     FILE *commandOutput;
12088     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12089     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12090     int nBuffers;
12091     int i;
12092     int archived;
12093     char *arcDir;
12094
12095     if (! cmailMsgLoaded) {
12096         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12097         return;
12098     }
12099
12100     if (nCmailGames == nCmailResults) {
12101         DisplayError(_("No unfinished games"), 0);
12102         return;
12103     }
12104
12105 #if CMAIL_PROHIBIT_REMAIL
12106     if (cmailMailedMove) {
12107       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);
12108         DisplayError(msg, 0);
12109         return;
12110     }
12111 #endif
12112
12113     if (! (cmailMailedMove || RegisterMove())) return;
12114
12115     if (   cmailMailedMove
12116         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12117       snprintf(string, MSG_SIZ, partCommandString,
12118                appData.debugMode ? " -v" : "", appData.cmailGameName);
12119         commandOutput = popen(string, "r");
12120
12121         if (commandOutput == NULL) {
12122             DisplayError(_("Failed to invoke cmail"), 0);
12123         } else {
12124             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12125                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12126             }
12127             if (nBuffers > 1) {
12128                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12129                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12130                 nBytes = MSG_SIZ - 1;
12131             } else {
12132                 (void) memcpy(msg, buffer, nBytes);
12133             }
12134             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12135
12136             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12137                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12138
12139                 archived = TRUE;
12140                 for (i = 0; i < nCmailGames; i ++) {
12141                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12142                         archived = FALSE;
12143                     }
12144                 }
12145                 if (   archived
12146                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12147                         != NULL)) {
12148                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12149                            arcDir,
12150                            appData.cmailGameName,
12151                            gameInfo.date);
12152                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12153                     cmailMsgLoaded = FALSE;
12154                 }
12155             }
12156
12157             DisplayInformation(msg);
12158             pclose(commandOutput);
12159         }
12160     } else {
12161         if ((*cmailMsg) != '\0') {
12162             DisplayInformation(cmailMsg);
12163         }
12164     }
12165
12166     return;
12167 #endif /* !WIN32 */
12168 }
12169
12170 char *
12171 CmailMsg()
12172 {
12173 #if WIN32
12174     return NULL;
12175 #else
12176     int  prependComma = 0;
12177     char number[5];
12178     char string[MSG_SIZ];       /* Space for game-list */
12179     int  i;
12180
12181     if (!cmailMsgLoaded) return "";
12182
12183     if (cmailMailedMove) {
12184       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12185     } else {
12186         /* Create a list of games left */
12187       snprintf(string, MSG_SIZ, "[");
12188         for (i = 0; i < nCmailGames; i ++) {
12189             if (! (   cmailMoveRegistered[i]
12190                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12191                 if (prependComma) {
12192                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12193                 } else {
12194                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12195                     prependComma = 1;
12196                 }
12197
12198                 strcat(string, number);
12199             }
12200         }
12201         strcat(string, "]");
12202
12203         if (nCmailMovesRegistered + nCmailResults == 0) {
12204             switch (nCmailGames) {
12205               case 1:
12206                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12207                 break;
12208
12209               case 2:
12210                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12211                 break;
12212
12213               default:
12214                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12215                          nCmailGames);
12216                 break;
12217             }
12218         } else {
12219             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12220               case 1:
12221                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12222                          string);
12223                 break;
12224
12225               case 0:
12226                 if (nCmailResults == nCmailGames) {
12227                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12228                 } else {
12229                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12230                 }
12231                 break;
12232
12233               default:
12234                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12235                          string);
12236             }
12237         }
12238     }
12239     return cmailMsg;
12240 #endif /* WIN32 */
12241 }
12242
12243 void
12244 ResetGameEvent()
12245 {
12246     if (gameMode == Training)
12247       SetTrainingModeOff();
12248
12249     Reset(TRUE, TRUE);
12250     cmailMsgLoaded = FALSE;
12251     if (appData.icsActive) {
12252       SendToICS(ics_prefix);
12253       SendToICS("refresh\n");
12254     }
12255 }
12256
12257 void
12258 ExitEvent(status)
12259      int status;
12260 {
12261     exiting++;
12262     if (exiting > 2) {
12263       /* Give up on clean exit */
12264       exit(status);
12265     }
12266     if (exiting > 1) {
12267       /* Keep trying for clean exit */
12268       return;
12269     }
12270
12271     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12272
12273     if (telnetISR != NULL) {
12274       RemoveInputSource(telnetISR);
12275     }
12276     if (icsPR != NoProc) {
12277       DestroyChildProcess(icsPR, TRUE);
12278     }
12279
12280     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12281     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12282
12283     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12284     /* make sure this other one finishes before killing it!                  */
12285     if(endingGame) { int count = 0;
12286         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12287         while(endingGame && count++ < 10) DoSleep(1);
12288         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12289     }
12290
12291     /* Kill off chess programs */
12292     if (first.pr != NoProc) {
12293         ExitAnalyzeMode();
12294
12295         DoSleep( appData.delayBeforeQuit );
12296         SendToProgram("quit\n", &first);
12297         DoSleep( appData.delayAfterQuit );
12298         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12299     }
12300     if (second.pr != NoProc) {
12301         DoSleep( appData.delayBeforeQuit );
12302         SendToProgram("quit\n", &second);
12303         DoSleep( appData.delayAfterQuit );
12304         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12305     }
12306     if (first.isr != NULL) {
12307         RemoveInputSource(first.isr);
12308     }
12309     if (second.isr != NULL) {
12310         RemoveInputSource(second.isr);
12311     }
12312
12313     ShutDownFrontEnd();
12314     exit(status);
12315 }
12316
12317 void
12318 PauseEvent()
12319 {
12320     if (appData.debugMode)
12321         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12322     if (pausing) {
12323         pausing = FALSE;
12324         ModeHighlight();
12325         if (gameMode == MachinePlaysWhite ||
12326             gameMode == MachinePlaysBlack) {
12327             StartClocks();
12328         } else {
12329             DisplayBothClocks();
12330         }
12331         if (gameMode == PlayFromGameFile) {
12332             if (appData.timeDelay >= 0)
12333                 AutoPlayGameLoop();
12334         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12335             Reset(FALSE, TRUE);
12336             SendToICS(ics_prefix);
12337             SendToICS("refresh\n");
12338         } else if (currentMove < forwardMostMove) {
12339             ForwardInner(forwardMostMove);
12340         }
12341         pauseExamInvalid = FALSE;
12342     } else {
12343         switch (gameMode) {
12344           default:
12345             return;
12346           case IcsExamining:
12347             pauseExamForwardMostMove = forwardMostMove;
12348             pauseExamInvalid = FALSE;
12349             /* fall through */
12350           case IcsObserving:
12351           case IcsPlayingWhite:
12352           case IcsPlayingBlack:
12353             pausing = TRUE;
12354             ModeHighlight();
12355             return;
12356           case PlayFromGameFile:
12357             (void) StopLoadGameTimer();
12358             pausing = TRUE;
12359             ModeHighlight();
12360             break;
12361           case BeginningOfGame:
12362             if (appData.icsActive) return;
12363             /* else fall through */
12364           case MachinePlaysWhite:
12365           case MachinePlaysBlack:
12366           case TwoMachinesPlay:
12367             if (forwardMostMove == 0)
12368               return;           /* don't pause if no one has moved */
12369             if ((gameMode == MachinePlaysWhite &&
12370                  !WhiteOnMove(forwardMostMove)) ||
12371                 (gameMode == MachinePlaysBlack &&
12372                  WhiteOnMove(forwardMostMove))) {
12373                 StopClocks();
12374             }
12375             pausing = TRUE;
12376             ModeHighlight();
12377             break;
12378         }
12379     }
12380 }
12381
12382 void
12383 EditCommentEvent()
12384 {
12385     char title[MSG_SIZ];
12386
12387     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12388       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12389     } else {
12390       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12391                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12392                parseList[currentMove - 1]);
12393     }
12394
12395     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12396 }
12397
12398
12399 void
12400 EditTagsEvent()
12401 {
12402     char *tags = PGNTags(&gameInfo);
12403     bookUp = FALSE;
12404     EditTagsPopUp(tags, NULL);
12405     free(tags);
12406 }
12407
12408 void
12409 AnalyzeModeEvent()
12410 {
12411     if (appData.noChessProgram || gameMode == AnalyzeMode)
12412       return;
12413
12414     if (gameMode != AnalyzeFile) {
12415         if (!appData.icsEngineAnalyze) {
12416                EditGameEvent();
12417                if (gameMode != EditGame) return;
12418         }
12419         ResurrectChessProgram();
12420         SendToProgram("analyze\n", &first);
12421         first.analyzing = TRUE;
12422         /*first.maybeThinking = TRUE;*/
12423         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12424         EngineOutputPopUp();
12425     }
12426     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12427     pausing = FALSE;
12428     ModeHighlight();
12429     SetGameInfo();
12430
12431     StartAnalysisClock();
12432     GetTimeMark(&lastNodeCountTime);
12433     lastNodeCount = 0;
12434 }
12435
12436 void
12437 AnalyzeFileEvent()
12438 {
12439     if (appData.noChessProgram || gameMode == AnalyzeFile)
12440       return;
12441
12442     if (gameMode != AnalyzeMode) {
12443         EditGameEvent();
12444         if (gameMode != EditGame) return;
12445         ResurrectChessProgram();
12446         SendToProgram("analyze\n", &first);
12447         first.analyzing = TRUE;
12448         /*first.maybeThinking = TRUE;*/
12449         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12450         EngineOutputPopUp();
12451     }
12452     gameMode = AnalyzeFile;
12453     pausing = FALSE;
12454     ModeHighlight();
12455     SetGameInfo();
12456
12457     StartAnalysisClock();
12458     GetTimeMark(&lastNodeCountTime);
12459     lastNodeCount = 0;
12460 }
12461
12462 void
12463 MachineWhiteEvent()
12464 {
12465     char buf[MSG_SIZ];
12466     char *bookHit = NULL;
12467
12468     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12469       return;
12470
12471
12472     if (gameMode == PlayFromGameFile ||
12473         gameMode == TwoMachinesPlay  ||
12474         gameMode == Training         ||
12475         gameMode == AnalyzeMode      ||
12476         gameMode == EndOfGame)
12477         EditGameEvent();
12478
12479     if (gameMode == EditPosition)
12480         EditPositionDone(TRUE);
12481
12482     if (!WhiteOnMove(currentMove)) {
12483         DisplayError(_("It is not White's turn"), 0);
12484         return;
12485     }
12486
12487     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12488       ExitAnalyzeMode();
12489
12490     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12491         gameMode == AnalyzeFile)
12492         TruncateGame();
12493
12494     ResurrectChessProgram();    /* in case it isn't running */
12495     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12496         gameMode = MachinePlaysWhite;
12497         ResetClocks();
12498     } else
12499     gameMode = MachinePlaysWhite;
12500     pausing = FALSE;
12501     ModeHighlight();
12502     SetGameInfo();
12503     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12504     DisplayTitle(buf);
12505     if (first.sendName) {
12506       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12507       SendToProgram(buf, &first);
12508     }
12509     if (first.sendTime) {
12510       if (first.useColors) {
12511         SendToProgram("black\n", &first); /*gnu kludge*/
12512       }
12513       SendTimeRemaining(&first, TRUE);
12514     }
12515     if (first.useColors) {
12516       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12517     }
12518     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12519     SetMachineThinkingEnables();
12520     first.maybeThinking = TRUE;
12521     StartClocks();
12522     firstMove = FALSE;
12523
12524     if (appData.autoFlipView && !flipView) {
12525       flipView = !flipView;
12526       DrawPosition(FALSE, NULL);
12527       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12528     }
12529
12530     if(bookHit) { // [HGM] book: simulate book reply
12531         static char bookMove[MSG_SIZ]; // a bit generous?
12532
12533         programStats.nodes = programStats.depth = programStats.time =
12534         programStats.score = programStats.got_only_move = 0;
12535         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12536
12537         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12538         strcat(bookMove, bookHit);
12539         HandleMachineMove(bookMove, &first);
12540     }
12541 }
12542
12543 void
12544 MachineBlackEvent()
12545 {
12546   char buf[MSG_SIZ];
12547   char *bookHit = NULL;
12548
12549     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12550         return;
12551
12552
12553     if (gameMode == PlayFromGameFile ||
12554         gameMode == TwoMachinesPlay  ||
12555         gameMode == Training         ||
12556         gameMode == AnalyzeMode      ||
12557         gameMode == EndOfGame)
12558         EditGameEvent();
12559
12560     if (gameMode == EditPosition)
12561         EditPositionDone(TRUE);
12562
12563     if (WhiteOnMove(currentMove)) {
12564         DisplayError(_("It is not Black's turn"), 0);
12565         return;
12566     }
12567
12568     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12569       ExitAnalyzeMode();
12570
12571     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12572         gameMode == AnalyzeFile)
12573         TruncateGame();
12574
12575     ResurrectChessProgram();    /* in case it isn't running */
12576     gameMode = MachinePlaysBlack;
12577     pausing = FALSE;
12578     ModeHighlight();
12579     SetGameInfo();
12580     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12581     DisplayTitle(buf);
12582     if (first.sendName) {
12583       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12584       SendToProgram(buf, &first);
12585     }
12586     if (first.sendTime) {
12587       if (first.useColors) {
12588         SendToProgram("white\n", &first); /*gnu kludge*/
12589       }
12590       SendTimeRemaining(&first, FALSE);
12591     }
12592     if (first.useColors) {
12593       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12594     }
12595     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12596     SetMachineThinkingEnables();
12597     first.maybeThinking = TRUE;
12598     StartClocks();
12599
12600     if (appData.autoFlipView && flipView) {
12601       flipView = !flipView;
12602       DrawPosition(FALSE, NULL);
12603       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12604     }
12605     if(bookHit) { // [HGM] book: simulate book reply
12606         static char bookMove[MSG_SIZ]; // a bit generous?
12607
12608         programStats.nodes = programStats.depth = programStats.time =
12609         programStats.score = programStats.got_only_move = 0;
12610         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12611
12612         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12613         strcat(bookMove, bookHit);
12614         HandleMachineMove(bookMove, &first);
12615     }
12616 }
12617
12618
12619 void
12620 DisplayTwoMachinesTitle()
12621 {
12622     char buf[MSG_SIZ];
12623     if (appData.matchGames > 0) {
12624         if (first.twoMachinesColor[0] == 'w') {
12625           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12626                    gameInfo.white, gameInfo.black,
12627                    first.matchWins, second.matchWins,
12628                    matchGame - 1 - (first.matchWins + second.matchWins));
12629         } else {
12630           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12631                    gameInfo.white, gameInfo.black,
12632                    second.matchWins, first.matchWins,
12633                    matchGame - 1 - (first.matchWins + second.matchWins));
12634         }
12635     } else {
12636       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12637     }
12638     DisplayTitle(buf);
12639 }
12640
12641 void
12642 SettingsMenuIfReady()
12643 {
12644   if (second.lastPing != second.lastPong) {
12645     DisplayMessage("", _("Waiting for second chess program"));
12646     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12647     return;
12648   }
12649   ThawUI();
12650   DisplayMessage("", "");
12651   SettingsPopUp(&second);
12652 }
12653
12654 int
12655 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12656 {
12657     char buf[MSG_SIZ];
12658     if (cps->pr == NULL) {
12659         StartChessProgram(cps);
12660         if (cps->protocolVersion == 1) {
12661           retry();
12662         } else {
12663           /* kludge: allow timeout for initial "feature" command */
12664           FreezeUI();
12665           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12666           DisplayMessage("", buf);
12667           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12668         }
12669         return 1;
12670     }
12671     return 0;
12672 }
12673
12674 void
12675 TwoMachinesEvent P((void))
12676 {
12677     int i;
12678     char buf[MSG_SIZ];
12679     ChessProgramState *onmove;
12680     char *bookHit = NULL;
12681     static int stalling = 0;
12682     TimeMark now;
12683     long wait;
12684
12685     if (appData.noChessProgram) return;
12686
12687     switch (gameMode) {
12688       case TwoMachinesPlay:
12689         return;
12690       case MachinePlaysWhite:
12691       case MachinePlaysBlack:
12692         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12693             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12694             return;
12695         }
12696         /* fall through */
12697       case BeginningOfGame:
12698       case PlayFromGameFile:
12699       case EndOfGame:
12700         EditGameEvent();
12701         if (gameMode != EditGame) return;
12702         break;
12703       case EditPosition:
12704         EditPositionDone(TRUE);
12705         break;
12706       case AnalyzeMode:
12707       case AnalyzeFile:
12708         ExitAnalyzeMode();
12709         break;
12710       case EditGame:
12711       default:
12712         break;
12713     }
12714
12715 //    forwardMostMove = currentMove;
12716     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12717
12718     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12719
12720     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12721     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12722       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12723       return;
12724     }
12725     if(!stalling) {
12726       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12727       SendToProgram("force\n", &second);
12728       stalling = 1;
12729       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12730       return;
12731     }
12732     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12733     if(appData.matchPause>10000 || appData.matchPause<10)
12734                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12735     wait = SubtractTimeMarks(&now, &pauseStart);
12736     if(wait < appData.matchPause) {
12737         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12738         return;
12739     }
12740     stalling = 0;
12741     DisplayMessage("", "");
12742     if (startedFromSetupPosition) {
12743         SendBoard(&second, backwardMostMove);
12744     if (appData.debugMode) {
12745         fprintf(debugFP, "Two Machines\n");
12746     }
12747     }
12748     for (i = backwardMostMove; i < forwardMostMove; i++) {
12749         SendMoveToProgram(i, &second);
12750     }
12751
12752     gameMode = TwoMachinesPlay;
12753     pausing = FALSE;
12754     ModeHighlight();
12755     SetGameInfo();
12756     DisplayTwoMachinesTitle();
12757     firstMove = TRUE;
12758     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12759         onmove = &first;
12760     } else {
12761         onmove = &second;
12762     }
12763     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12764     SendToProgram(first.computerString, &first);
12765     if (first.sendName) {
12766       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12767       SendToProgram(buf, &first);
12768     }
12769     SendToProgram(second.computerString, &second);
12770     if (second.sendName) {
12771       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12772       SendToProgram(buf, &second);
12773     }
12774
12775     ResetClocks();
12776     if (!first.sendTime || !second.sendTime) {
12777         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12778         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12779     }
12780     if (onmove->sendTime) {
12781       if (onmove->useColors) {
12782         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12783       }
12784       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12785     }
12786     if (onmove->useColors) {
12787       SendToProgram(onmove->twoMachinesColor, onmove);
12788     }
12789     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12790 //    SendToProgram("go\n", onmove);
12791     onmove->maybeThinking = TRUE;
12792     SetMachineThinkingEnables();
12793
12794     StartClocks();
12795
12796     if(bookHit) { // [HGM] book: simulate book reply
12797         static char bookMove[MSG_SIZ]; // a bit generous?
12798
12799         programStats.nodes = programStats.depth = programStats.time =
12800         programStats.score = programStats.got_only_move = 0;
12801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12802
12803         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12804         strcat(bookMove, bookHit);
12805         savedMessage = bookMove; // args for deferred call
12806         savedState = onmove;
12807         ScheduleDelayedEvent(DeferredBookMove, 1);
12808     }
12809 }
12810
12811 void
12812 TrainingEvent()
12813 {
12814     if (gameMode == Training) {
12815       SetTrainingModeOff();
12816       gameMode = PlayFromGameFile;
12817       DisplayMessage("", _("Training mode off"));
12818     } else {
12819       gameMode = Training;
12820       animateTraining = appData.animate;
12821
12822       /* make sure we are not already at the end of the game */
12823       if (currentMove < forwardMostMove) {
12824         SetTrainingModeOn();
12825         DisplayMessage("", _("Training mode on"));
12826       } else {
12827         gameMode = PlayFromGameFile;
12828         DisplayError(_("Already at end of game"), 0);
12829       }
12830     }
12831     ModeHighlight();
12832 }
12833
12834 void
12835 IcsClientEvent()
12836 {
12837     if (!appData.icsActive) return;
12838     switch (gameMode) {
12839       case IcsPlayingWhite:
12840       case IcsPlayingBlack:
12841       case IcsObserving:
12842       case IcsIdle:
12843       case BeginningOfGame:
12844       case IcsExamining:
12845         return;
12846
12847       case EditGame:
12848         break;
12849
12850       case EditPosition:
12851         EditPositionDone(TRUE);
12852         break;
12853
12854       case AnalyzeMode:
12855       case AnalyzeFile:
12856         ExitAnalyzeMode();
12857         break;
12858
12859       default:
12860         EditGameEvent();
12861         break;
12862     }
12863
12864     gameMode = IcsIdle;
12865     ModeHighlight();
12866     return;
12867 }
12868
12869
12870 void
12871 EditGameEvent()
12872 {
12873     int i;
12874
12875     switch (gameMode) {
12876       case Training:
12877         SetTrainingModeOff();
12878         break;
12879       case MachinePlaysWhite:
12880       case MachinePlaysBlack:
12881       case BeginningOfGame:
12882         SendToProgram("force\n", &first);
12883         SetUserThinkingEnables();
12884         break;
12885       case PlayFromGameFile:
12886         (void) StopLoadGameTimer();
12887         if (gameFileFP != NULL) {
12888             gameFileFP = NULL;
12889         }
12890         break;
12891       case EditPosition:
12892         EditPositionDone(TRUE);
12893         break;
12894       case AnalyzeMode:
12895       case AnalyzeFile:
12896         ExitAnalyzeMode();
12897         SendToProgram("force\n", &first);
12898         break;
12899       case TwoMachinesPlay:
12900         GameEnds(EndOfFile, NULL, GE_PLAYER);
12901         ResurrectChessProgram();
12902         SetUserThinkingEnables();
12903         break;
12904       case EndOfGame:
12905         ResurrectChessProgram();
12906         break;
12907       case IcsPlayingBlack:
12908       case IcsPlayingWhite:
12909         DisplayError(_("Warning: You are still playing a game"), 0);
12910         break;
12911       case IcsObserving:
12912         DisplayError(_("Warning: You are still observing a game"), 0);
12913         break;
12914       case IcsExamining:
12915         DisplayError(_("Warning: You are still examining a game"), 0);
12916         break;
12917       case IcsIdle:
12918         break;
12919       case EditGame:
12920       default:
12921         return;
12922     }
12923
12924     pausing = FALSE;
12925     StopClocks();
12926     first.offeredDraw = second.offeredDraw = 0;
12927
12928     if (gameMode == PlayFromGameFile) {
12929         whiteTimeRemaining = timeRemaining[0][currentMove];
12930         blackTimeRemaining = timeRemaining[1][currentMove];
12931         DisplayTitle("");
12932     }
12933
12934     if (gameMode == MachinePlaysWhite ||
12935         gameMode == MachinePlaysBlack ||
12936         gameMode == TwoMachinesPlay ||
12937         gameMode == EndOfGame) {
12938         i = forwardMostMove;
12939         while (i > currentMove) {
12940             SendToProgram("undo\n", &first);
12941             i--;
12942         }
12943         whiteTimeRemaining = timeRemaining[0][currentMove];
12944         blackTimeRemaining = timeRemaining[1][currentMove];
12945         DisplayBothClocks();
12946         if (whiteFlag || blackFlag) {
12947             whiteFlag = blackFlag = 0;
12948         }
12949         DisplayTitle("");
12950     }
12951
12952     gameMode = EditGame;
12953     ModeHighlight();
12954     SetGameInfo();
12955 }
12956
12957
12958 void
12959 EditPositionEvent()
12960 {
12961     if (gameMode == EditPosition) {
12962         EditGameEvent();
12963         return;
12964     }
12965
12966     EditGameEvent();
12967     if (gameMode != EditGame) return;
12968
12969     gameMode = EditPosition;
12970     ModeHighlight();
12971     SetGameInfo();
12972     if (currentMove > 0)
12973       CopyBoard(boards[0], boards[currentMove]);
12974
12975     blackPlaysFirst = !WhiteOnMove(currentMove);
12976     ResetClocks();
12977     currentMove = forwardMostMove = backwardMostMove = 0;
12978     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12979     DisplayMove(-1);
12980 }
12981
12982 void
12983 ExitAnalyzeMode()
12984 {
12985     /* [DM] icsEngineAnalyze - possible call from other functions */
12986     if (appData.icsEngineAnalyze) {
12987         appData.icsEngineAnalyze = FALSE;
12988
12989         DisplayMessage("",_("Close ICS engine analyze..."));
12990     }
12991     if (first.analysisSupport && first.analyzing) {
12992       SendToProgram("exit\n", &first);
12993       first.analyzing = FALSE;
12994     }
12995     thinkOutput[0] = NULLCHAR;
12996 }
12997
12998 void
12999 EditPositionDone(Boolean fakeRights)
13000 {
13001     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13002
13003     startedFromSetupPosition = TRUE;
13004     InitChessProgram(&first, FALSE);
13005     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13006       boards[0][EP_STATUS] = EP_NONE;
13007       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13008     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13009         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13010         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13011       } else boards[0][CASTLING][2] = NoRights;
13012     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13013         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13014         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13015       } else boards[0][CASTLING][5] = NoRights;
13016     }
13017     SendToProgram("force\n", &first);
13018     if (blackPlaysFirst) {
13019         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13020         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13021         currentMove = forwardMostMove = backwardMostMove = 1;
13022         CopyBoard(boards[1], boards[0]);
13023     } else {
13024         currentMove = forwardMostMove = backwardMostMove = 0;
13025     }
13026     SendBoard(&first, forwardMostMove);
13027     if (appData.debugMode) {
13028         fprintf(debugFP, "EditPosDone\n");
13029     }
13030     DisplayTitle("");
13031     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13032     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13033     gameMode = EditGame;
13034     ModeHighlight();
13035     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13036     ClearHighlights(); /* [AS] */
13037 }
13038
13039 /* Pause for `ms' milliseconds */
13040 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13041 void
13042 TimeDelay(ms)
13043      long ms;
13044 {
13045     TimeMark m1, m2;
13046
13047     GetTimeMark(&m1);
13048     do {
13049         GetTimeMark(&m2);
13050     } while (SubtractTimeMarks(&m2, &m1) < ms);
13051 }
13052
13053 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13054 void
13055 SendMultiLineToICS(buf)
13056      char *buf;
13057 {
13058     char temp[MSG_SIZ+1], *p;
13059     int len;
13060
13061     len = strlen(buf);
13062     if (len > MSG_SIZ)
13063       len = MSG_SIZ;
13064
13065     strncpy(temp, buf, len);
13066     temp[len] = 0;
13067
13068     p = temp;
13069     while (*p) {
13070         if (*p == '\n' || *p == '\r')
13071           *p = ' ';
13072         ++p;
13073     }
13074
13075     strcat(temp, "\n");
13076     SendToICS(temp);
13077     SendToPlayer(temp, strlen(temp));
13078 }
13079
13080 void
13081 SetWhiteToPlayEvent()
13082 {
13083     if (gameMode == EditPosition) {
13084         blackPlaysFirst = FALSE;
13085         DisplayBothClocks();    /* works because currentMove is 0 */
13086     } else if (gameMode == IcsExamining) {
13087         SendToICS(ics_prefix);
13088         SendToICS("tomove white\n");
13089     }
13090 }
13091
13092 void
13093 SetBlackToPlayEvent()
13094 {
13095     if (gameMode == EditPosition) {
13096         blackPlaysFirst = TRUE;
13097         currentMove = 1;        /* kludge */
13098         DisplayBothClocks();
13099         currentMove = 0;
13100     } else if (gameMode == IcsExamining) {
13101         SendToICS(ics_prefix);
13102         SendToICS("tomove black\n");
13103     }
13104 }
13105
13106 void
13107 EditPositionMenuEvent(selection, x, y)
13108      ChessSquare selection;
13109      int x, y;
13110 {
13111     char buf[MSG_SIZ];
13112     ChessSquare piece = boards[0][y][x];
13113
13114     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13115
13116     switch (selection) {
13117       case ClearBoard:
13118         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13119             SendToICS(ics_prefix);
13120             SendToICS("bsetup clear\n");
13121         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13122             SendToICS(ics_prefix);
13123             SendToICS("clearboard\n");
13124         } else {
13125             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13126                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13127                 for (y = 0; y < BOARD_HEIGHT; y++) {
13128                     if (gameMode == IcsExamining) {
13129                         if (boards[currentMove][y][x] != EmptySquare) {
13130                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13131                                     AAA + x, ONE + y);
13132                             SendToICS(buf);
13133                         }
13134                     } else {
13135                         boards[0][y][x] = p;
13136                     }
13137                 }
13138             }
13139         }
13140         if (gameMode == EditPosition) {
13141             DrawPosition(FALSE, boards[0]);
13142         }
13143         break;
13144
13145       case WhitePlay:
13146         SetWhiteToPlayEvent();
13147         break;
13148
13149       case BlackPlay:
13150         SetBlackToPlayEvent();
13151         break;
13152
13153       case EmptySquare:
13154         if (gameMode == IcsExamining) {
13155             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13156             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13157             SendToICS(buf);
13158         } else {
13159             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13160                 if(x == BOARD_LEFT-2) {
13161                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13162                     boards[0][y][1] = 0;
13163                 } else
13164                 if(x == BOARD_RGHT+1) {
13165                     if(y >= gameInfo.holdingsSize) break;
13166                     boards[0][y][BOARD_WIDTH-2] = 0;
13167                 } else break;
13168             }
13169             boards[0][y][x] = EmptySquare;
13170             DrawPosition(FALSE, boards[0]);
13171         }
13172         break;
13173
13174       case PromotePiece:
13175         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13176            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13177             selection = (ChessSquare) (PROMOTED piece);
13178         } else if(piece == EmptySquare) selection = WhiteSilver;
13179         else selection = (ChessSquare)((int)piece - 1);
13180         goto defaultlabel;
13181
13182       case DemotePiece:
13183         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13184            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13185             selection = (ChessSquare) (DEMOTED piece);
13186         } else if(piece == EmptySquare) selection = BlackSilver;
13187         else selection = (ChessSquare)((int)piece + 1);
13188         goto defaultlabel;
13189
13190       case WhiteQueen:
13191       case BlackQueen:
13192         if(gameInfo.variant == VariantShatranj ||
13193            gameInfo.variant == VariantXiangqi  ||
13194            gameInfo.variant == VariantCourier  ||
13195            gameInfo.variant == VariantMakruk     )
13196             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13197         goto defaultlabel;
13198
13199       case WhiteKing:
13200       case BlackKing:
13201         if(gameInfo.variant == VariantXiangqi)
13202             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13203         if(gameInfo.variant == VariantKnightmate)
13204             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13205       default:
13206         defaultlabel:
13207         if (gameMode == IcsExamining) {
13208             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13209             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13210                      PieceToChar(selection), AAA + x, ONE + y);
13211             SendToICS(buf);
13212         } else {
13213             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13214                 int n;
13215                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13216                     n = PieceToNumber(selection - BlackPawn);
13217                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13218                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13219                     boards[0][BOARD_HEIGHT-1-n][1]++;
13220                 } else
13221                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13222                     n = PieceToNumber(selection);
13223                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13224                     boards[0][n][BOARD_WIDTH-1] = selection;
13225                     boards[0][n][BOARD_WIDTH-2]++;
13226                 }
13227             } else
13228             boards[0][y][x] = selection;
13229             DrawPosition(TRUE, boards[0]);
13230         }
13231         break;
13232     }
13233 }
13234
13235
13236 void
13237 DropMenuEvent(selection, x, y)
13238      ChessSquare selection;
13239      int x, y;
13240 {
13241     ChessMove moveType;
13242
13243     switch (gameMode) {
13244       case IcsPlayingWhite:
13245       case MachinePlaysBlack:
13246         if (!WhiteOnMove(currentMove)) {
13247             DisplayMoveError(_("It is Black's turn"));
13248             return;
13249         }
13250         moveType = WhiteDrop;
13251         break;
13252       case IcsPlayingBlack:
13253       case MachinePlaysWhite:
13254         if (WhiteOnMove(currentMove)) {
13255             DisplayMoveError(_("It is White's turn"));
13256             return;
13257         }
13258         moveType = BlackDrop;
13259         break;
13260       case EditGame:
13261         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13262         break;
13263       default:
13264         return;
13265     }
13266
13267     if (moveType == BlackDrop && selection < BlackPawn) {
13268       selection = (ChessSquare) ((int) selection
13269                                  + (int) BlackPawn - (int) WhitePawn);
13270     }
13271     if (boards[currentMove][y][x] != EmptySquare) {
13272         DisplayMoveError(_("That square is occupied"));
13273         return;
13274     }
13275
13276     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13277 }
13278
13279 void
13280 AcceptEvent()
13281 {
13282     /* Accept a pending offer of any kind from opponent */
13283
13284     if (appData.icsActive) {
13285         SendToICS(ics_prefix);
13286         SendToICS("accept\n");
13287     } else if (cmailMsgLoaded) {
13288         if (currentMove == cmailOldMove &&
13289             commentList[cmailOldMove] != NULL &&
13290             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13291                    "Black offers a draw" : "White offers a draw")) {
13292             TruncateGame();
13293             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13294             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13295         } else {
13296             DisplayError(_("There is no pending offer on this move"), 0);
13297             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13298         }
13299     } else {
13300         /* Not used for offers from chess program */
13301     }
13302 }
13303
13304 void
13305 DeclineEvent()
13306 {
13307     /* Decline a pending offer of any kind from opponent */
13308
13309     if (appData.icsActive) {
13310         SendToICS(ics_prefix);
13311         SendToICS("decline\n");
13312     } else if (cmailMsgLoaded) {
13313         if (currentMove == cmailOldMove &&
13314             commentList[cmailOldMove] != NULL &&
13315             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13316                    "Black offers a draw" : "White offers a draw")) {
13317 #ifdef NOTDEF
13318             AppendComment(cmailOldMove, "Draw declined", TRUE);
13319             DisplayComment(cmailOldMove - 1, "Draw declined");
13320 #endif /*NOTDEF*/
13321         } else {
13322             DisplayError(_("There is no pending offer on this move"), 0);
13323         }
13324     } else {
13325         /* Not used for offers from chess program */
13326     }
13327 }
13328
13329 void
13330 RematchEvent()
13331 {
13332     /* Issue ICS rematch command */
13333     if (appData.icsActive) {
13334         SendToICS(ics_prefix);
13335         SendToICS("rematch\n");
13336     }
13337 }
13338
13339 void
13340 CallFlagEvent()
13341 {
13342     /* Call your opponent's flag (claim a win on time) */
13343     if (appData.icsActive) {
13344         SendToICS(ics_prefix);
13345         SendToICS("flag\n");
13346     } else {
13347         switch (gameMode) {
13348           default:
13349             return;
13350           case MachinePlaysWhite:
13351             if (whiteFlag) {
13352                 if (blackFlag)
13353                   GameEnds(GameIsDrawn, "Both players ran out of time",
13354                            GE_PLAYER);
13355                 else
13356                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13357             } else {
13358                 DisplayError(_("Your opponent is not out of time"), 0);
13359             }
13360             break;
13361           case MachinePlaysBlack:
13362             if (blackFlag) {
13363                 if (whiteFlag)
13364                   GameEnds(GameIsDrawn, "Both players ran out of time",
13365                            GE_PLAYER);
13366                 else
13367                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13368             } else {
13369                 DisplayError(_("Your opponent is not out of time"), 0);
13370             }
13371             break;
13372         }
13373     }
13374 }
13375
13376 void
13377 ClockClick(int which)
13378 {       // [HGM] code moved to back-end from winboard.c
13379         if(which) { // black clock
13380           if (gameMode == EditPosition || gameMode == IcsExamining) {
13381             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13382             SetBlackToPlayEvent();
13383           } else if (gameMode == EditGame || shiftKey) {
13384             AdjustClock(which, -1);
13385           } else if (gameMode == IcsPlayingWhite ||
13386                      gameMode == MachinePlaysBlack) {
13387             CallFlagEvent();
13388           }
13389         } else { // white clock
13390           if (gameMode == EditPosition || gameMode == IcsExamining) {
13391             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13392             SetWhiteToPlayEvent();
13393           } else if (gameMode == EditGame || shiftKey) {
13394             AdjustClock(which, -1);
13395           } else if (gameMode == IcsPlayingBlack ||
13396                    gameMode == MachinePlaysWhite) {
13397             CallFlagEvent();
13398           }
13399         }
13400 }
13401
13402 void
13403 DrawEvent()
13404 {
13405     /* Offer draw or accept pending draw offer from opponent */
13406
13407     if (appData.icsActive) {
13408         /* Note: tournament rules require draw offers to be
13409            made after you make your move but before you punch
13410            your clock.  Currently ICS doesn't let you do that;
13411            instead, you immediately punch your clock after making
13412            a move, but you can offer a draw at any time. */
13413
13414         SendToICS(ics_prefix);
13415         SendToICS("draw\n");
13416         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13417     } else if (cmailMsgLoaded) {
13418         if (currentMove == cmailOldMove &&
13419             commentList[cmailOldMove] != NULL &&
13420             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13421                    "Black offers a draw" : "White offers a draw")) {
13422             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13423             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13424         } else if (currentMove == cmailOldMove + 1) {
13425             char *offer = WhiteOnMove(cmailOldMove) ?
13426               "White offers a draw" : "Black offers a draw";
13427             AppendComment(currentMove, offer, TRUE);
13428             DisplayComment(currentMove - 1, offer);
13429             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13430         } else {
13431             DisplayError(_("You must make your move before offering a draw"), 0);
13432             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13433         }
13434     } else if (first.offeredDraw) {
13435         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13436     } else {
13437         if (first.sendDrawOffers) {
13438             SendToProgram("draw\n", &first);
13439             userOfferedDraw = TRUE;
13440         }
13441     }
13442 }
13443
13444 void
13445 AdjournEvent()
13446 {
13447     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13448
13449     if (appData.icsActive) {
13450         SendToICS(ics_prefix);
13451         SendToICS("adjourn\n");
13452     } else {
13453         /* Currently GNU Chess doesn't offer or accept Adjourns */
13454     }
13455 }
13456
13457
13458 void
13459 AbortEvent()
13460 {
13461     /* Offer Abort or accept pending Abort offer from opponent */
13462
13463     if (appData.icsActive) {
13464         SendToICS(ics_prefix);
13465         SendToICS("abort\n");
13466     } else {
13467         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13468     }
13469 }
13470
13471 void
13472 ResignEvent()
13473 {
13474     /* Resign.  You can do this even if it's not your turn. */
13475
13476     if (appData.icsActive) {
13477         SendToICS(ics_prefix);
13478         SendToICS("resign\n");
13479     } else {
13480         switch (gameMode) {
13481           case MachinePlaysWhite:
13482             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13483             break;
13484           case MachinePlaysBlack:
13485             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13486             break;
13487           case EditGame:
13488             if (cmailMsgLoaded) {
13489                 TruncateGame();
13490                 if (WhiteOnMove(cmailOldMove)) {
13491                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13492                 } else {
13493                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13494                 }
13495                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13496             }
13497             break;
13498           default:
13499             break;
13500         }
13501     }
13502 }
13503
13504
13505 void
13506 StopObservingEvent()
13507 {
13508     /* Stop observing current games */
13509     SendToICS(ics_prefix);
13510     SendToICS("unobserve\n");
13511 }
13512
13513 void
13514 StopExaminingEvent()
13515 {
13516     /* Stop observing current game */
13517     SendToICS(ics_prefix);
13518     SendToICS("unexamine\n");
13519 }
13520
13521 void
13522 ForwardInner(target)
13523      int target;
13524 {
13525     int limit;
13526
13527     if (appData.debugMode)
13528         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13529                 target, currentMove, forwardMostMove);
13530
13531     if (gameMode == EditPosition)
13532       return;
13533
13534     if (gameMode == PlayFromGameFile && !pausing)
13535       PauseEvent();
13536
13537     if (gameMode == IcsExamining && pausing)
13538       limit = pauseExamForwardMostMove;
13539     else
13540       limit = forwardMostMove;
13541
13542     if (target > limit) target = limit;
13543
13544     if (target > 0 && moveList[target - 1][0]) {
13545         int fromX, fromY, toX, toY;
13546         toX = moveList[target - 1][2] - AAA;
13547         toY = moveList[target - 1][3] - ONE;
13548         if (moveList[target - 1][1] == '@') {
13549             if (appData.highlightLastMove) {
13550                 SetHighlights(-1, -1, toX, toY);
13551             }
13552         } else {
13553             fromX = moveList[target - 1][0] - AAA;
13554             fromY = moveList[target - 1][1] - ONE;
13555             if (target == currentMove + 1) {
13556                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13557             }
13558             if (appData.highlightLastMove) {
13559                 SetHighlights(fromX, fromY, toX, toY);
13560             }
13561         }
13562     }
13563     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13564         gameMode == Training || gameMode == PlayFromGameFile ||
13565         gameMode == AnalyzeFile) {
13566         while (currentMove < target) {
13567             SendMoveToProgram(currentMove++, &first);
13568         }
13569     } else {
13570         currentMove = target;
13571     }
13572
13573     if (gameMode == EditGame || gameMode == EndOfGame) {
13574         whiteTimeRemaining = timeRemaining[0][currentMove];
13575         blackTimeRemaining = timeRemaining[1][currentMove];
13576     }
13577     DisplayBothClocks();
13578     DisplayMove(currentMove - 1);
13579     DrawPosition(FALSE, boards[currentMove]);
13580     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13581     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13582         DisplayComment(currentMove - 1, commentList[currentMove]);
13583     }
13584     DisplayBook(currentMove);
13585 }
13586
13587
13588 void
13589 ForwardEvent()
13590 {
13591     if (gameMode == IcsExamining && !pausing) {
13592         SendToICS(ics_prefix);
13593         SendToICS("forward\n");
13594     } else {
13595         ForwardInner(currentMove + 1);
13596     }
13597 }
13598
13599 void
13600 ToEndEvent()
13601 {
13602     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13603         /* to optimze, we temporarily turn off analysis mode while we feed
13604          * the remaining moves to the engine. Otherwise we get analysis output
13605          * after each move.
13606          */
13607         if (first.analysisSupport) {
13608           SendToProgram("exit\nforce\n", &first);
13609           first.analyzing = FALSE;
13610         }
13611     }
13612
13613     if (gameMode == IcsExamining && !pausing) {
13614         SendToICS(ics_prefix);
13615         SendToICS("forward 999999\n");
13616     } else {
13617         ForwardInner(forwardMostMove);
13618     }
13619
13620     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13621         /* we have fed all the moves, so reactivate analysis mode */
13622         SendToProgram("analyze\n", &first);
13623         first.analyzing = TRUE;
13624         /*first.maybeThinking = TRUE;*/
13625         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13626     }
13627 }
13628
13629 void
13630 BackwardInner(target)
13631      int target;
13632 {
13633     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13634
13635     if (appData.debugMode)
13636         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13637                 target, currentMove, forwardMostMove);
13638
13639     if (gameMode == EditPosition) return;
13640     if (currentMove <= backwardMostMove) {
13641         ClearHighlights();
13642         DrawPosition(full_redraw, boards[currentMove]);
13643         return;
13644     }
13645     if (gameMode == PlayFromGameFile && !pausing)
13646       PauseEvent();
13647
13648     if (moveList[target][0]) {
13649         int fromX, fromY, toX, toY;
13650         toX = moveList[target][2] - AAA;
13651         toY = moveList[target][3] - ONE;
13652         if (moveList[target][1] == '@') {
13653             if (appData.highlightLastMove) {
13654                 SetHighlights(-1, -1, toX, toY);
13655             }
13656         } else {
13657             fromX = moveList[target][0] - AAA;
13658             fromY = moveList[target][1] - ONE;
13659             if (target == currentMove - 1) {
13660                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13661             }
13662             if (appData.highlightLastMove) {
13663                 SetHighlights(fromX, fromY, toX, toY);
13664             }
13665         }
13666     }
13667     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13668         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13669         while (currentMove > target) {
13670             SendToProgram("undo\n", &first);
13671             currentMove--;
13672         }
13673     } else {
13674         currentMove = target;
13675     }
13676
13677     if (gameMode == EditGame || gameMode == EndOfGame) {
13678         whiteTimeRemaining = timeRemaining[0][currentMove];
13679         blackTimeRemaining = timeRemaining[1][currentMove];
13680     }
13681     DisplayBothClocks();
13682     DisplayMove(currentMove - 1);
13683     DrawPosition(full_redraw, boards[currentMove]);
13684     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13685     // [HGM] PV info: routine tests if comment empty
13686     DisplayComment(currentMove - 1, commentList[currentMove]);
13687     DisplayBook(currentMove);
13688 }
13689
13690 void
13691 BackwardEvent()
13692 {
13693     if (gameMode == IcsExamining && !pausing) {
13694         SendToICS(ics_prefix);
13695         SendToICS("backward\n");
13696     } else {
13697         BackwardInner(currentMove - 1);
13698     }
13699 }
13700
13701 void
13702 ToStartEvent()
13703 {
13704     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13705         /* to optimize, we temporarily turn off analysis mode while we undo
13706          * all the moves. Otherwise we get analysis output after each undo.
13707          */
13708         if (first.analysisSupport) {
13709           SendToProgram("exit\nforce\n", &first);
13710           first.analyzing = FALSE;
13711         }
13712     }
13713
13714     if (gameMode == IcsExamining && !pausing) {
13715         SendToICS(ics_prefix);
13716         SendToICS("backward 999999\n");
13717     } else {
13718         BackwardInner(backwardMostMove);
13719     }
13720
13721     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13722         /* we have fed all the moves, so reactivate analysis mode */
13723         SendToProgram("analyze\n", &first);
13724         first.analyzing = TRUE;
13725         /*first.maybeThinking = TRUE;*/
13726         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13727     }
13728 }
13729
13730 void
13731 ToNrEvent(int to)
13732 {
13733   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13734   if (to >= forwardMostMove) to = forwardMostMove;
13735   if (to <= backwardMostMove) to = backwardMostMove;
13736   if (to < currentMove) {
13737     BackwardInner(to);
13738   } else {
13739     ForwardInner(to);
13740   }
13741 }
13742
13743 void
13744 RevertEvent(Boolean annotate)
13745 {
13746     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13747         return;
13748     }
13749     if (gameMode != IcsExamining) {
13750         DisplayError(_("You are not examining a game"), 0);
13751         return;
13752     }
13753     if (pausing) {
13754         DisplayError(_("You can't revert while pausing"), 0);
13755         return;
13756     }
13757     SendToICS(ics_prefix);
13758     SendToICS("revert\n");
13759 }
13760
13761 void
13762 RetractMoveEvent()
13763 {
13764     switch (gameMode) {
13765       case MachinePlaysWhite:
13766       case MachinePlaysBlack:
13767         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13768             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13769             return;
13770         }
13771         if (forwardMostMove < 2) return;
13772         currentMove = forwardMostMove = forwardMostMove - 2;
13773         whiteTimeRemaining = timeRemaining[0][currentMove];
13774         blackTimeRemaining = timeRemaining[1][currentMove];
13775         DisplayBothClocks();
13776         DisplayMove(currentMove - 1);
13777         ClearHighlights();/*!! could figure this out*/
13778         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13779         SendToProgram("remove\n", &first);
13780         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13781         break;
13782
13783       case BeginningOfGame:
13784       default:
13785         break;
13786
13787       case IcsPlayingWhite:
13788       case IcsPlayingBlack:
13789         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13790             SendToICS(ics_prefix);
13791             SendToICS("takeback 2\n");
13792         } else {
13793             SendToICS(ics_prefix);
13794             SendToICS("takeback 1\n");
13795         }
13796         break;
13797     }
13798 }
13799
13800 void
13801 MoveNowEvent()
13802 {
13803     ChessProgramState *cps;
13804
13805     switch (gameMode) {
13806       case MachinePlaysWhite:
13807         if (!WhiteOnMove(forwardMostMove)) {
13808             DisplayError(_("It is your turn"), 0);
13809             return;
13810         }
13811         cps = &first;
13812         break;
13813       case MachinePlaysBlack:
13814         if (WhiteOnMove(forwardMostMove)) {
13815             DisplayError(_("It is your turn"), 0);
13816             return;
13817         }
13818         cps = &first;
13819         break;
13820       case TwoMachinesPlay:
13821         if (WhiteOnMove(forwardMostMove) ==
13822             (first.twoMachinesColor[0] == 'w')) {
13823             cps = &first;
13824         } else {
13825             cps = &second;
13826         }
13827         break;
13828       case BeginningOfGame:
13829       default:
13830         return;
13831     }
13832     SendToProgram("?\n", cps);
13833 }
13834
13835 void
13836 TruncateGameEvent()
13837 {
13838     EditGameEvent();
13839     if (gameMode != EditGame) return;
13840     TruncateGame();
13841 }
13842
13843 void
13844 TruncateGame()
13845 {
13846     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13847     if (forwardMostMove > currentMove) {
13848         if (gameInfo.resultDetails != NULL) {
13849             free(gameInfo.resultDetails);
13850             gameInfo.resultDetails = NULL;
13851             gameInfo.result = GameUnfinished;
13852         }
13853         forwardMostMove = currentMove;
13854         HistorySet(parseList, backwardMostMove, forwardMostMove,
13855                    currentMove-1);
13856     }
13857 }
13858
13859 void
13860 HintEvent()
13861 {
13862     if (appData.noChessProgram) return;
13863     switch (gameMode) {
13864       case MachinePlaysWhite:
13865         if (WhiteOnMove(forwardMostMove)) {
13866             DisplayError(_("Wait until your turn"), 0);
13867             return;
13868         }
13869         break;
13870       case BeginningOfGame:
13871       case MachinePlaysBlack:
13872         if (!WhiteOnMove(forwardMostMove)) {
13873             DisplayError(_("Wait until your turn"), 0);
13874             return;
13875         }
13876         break;
13877       default:
13878         DisplayError(_("No hint available"), 0);
13879         return;
13880     }
13881     SendToProgram("hint\n", &first);
13882     hintRequested = TRUE;
13883 }
13884
13885 void
13886 BookEvent()
13887 {
13888     if (appData.noChessProgram) return;
13889     switch (gameMode) {
13890       case MachinePlaysWhite:
13891         if (WhiteOnMove(forwardMostMove)) {
13892             DisplayError(_("Wait until your turn"), 0);
13893             return;
13894         }
13895         break;
13896       case BeginningOfGame:
13897       case MachinePlaysBlack:
13898         if (!WhiteOnMove(forwardMostMove)) {
13899             DisplayError(_("Wait until your turn"), 0);
13900             return;
13901         }
13902         break;
13903       case EditPosition:
13904         EditPositionDone(TRUE);
13905         break;
13906       case TwoMachinesPlay:
13907         return;
13908       default:
13909         break;
13910     }
13911     SendToProgram("bk\n", &first);
13912     bookOutput[0] = NULLCHAR;
13913     bookRequested = TRUE;
13914 }
13915
13916 void
13917 AboutGameEvent()
13918 {
13919     char *tags = PGNTags(&gameInfo);
13920     TagsPopUp(tags, CmailMsg());
13921     free(tags);
13922 }
13923
13924 /* end button procedures */
13925
13926 void
13927 PrintPosition(fp, move)
13928      FILE *fp;
13929      int move;
13930 {
13931     int i, j;
13932
13933     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13934         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13935             char c = PieceToChar(boards[move][i][j]);
13936             fputc(c == 'x' ? '.' : c, fp);
13937             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13938         }
13939     }
13940     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13941       fprintf(fp, "white to play\n");
13942     else
13943       fprintf(fp, "black to play\n");
13944 }
13945
13946 void
13947 PrintOpponents(fp)
13948      FILE *fp;
13949 {
13950     if (gameInfo.white != NULL) {
13951         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13952     } else {
13953         fprintf(fp, "\n");
13954     }
13955 }
13956
13957 /* Find last component of program's own name, using some heuristics */
13958 void
13959 TidyProgramName(prog, host, buf)
13960      char *prog, *host, buf[MSG_SIZ];
13961 {
13962     char *p, *q;
13963     int local = (strcmp(host, "localhost") == 0);
13964     while (!local && (p = strchr(prog, ';')) != NULL) {
13965         p++;
13966         while (*p == ' ') p++;
13967         prog = p;
13968     }
13969     if (*prog == '"' || *prog == '\'') {
13970         q = strchr(prog + 1, *prog);
13971     } else {
13972         q = strchr(prog, ' ');
13973     }
13974     if (q == NULL) q = prog + strlen(prog);
13975     p = q;
13976     while (p >= prog && *p != '/' && *p != '\\') p--;
13977     p++;
13978     if(p == prog && *p == '"') p++;
13979     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13980     memcpy(buf, p, q - p);
13981     buf[q - p] = NULLCHAR;
13982     if (!local) {
13983         strcat(buf, "@");
13984         strcat(buf, host);
13985     }
13986 }
13987
13988 char *
13989 TimeControlTagValue()
13990 {
13991     char buf[MSG_SIZ];
13992     if (!appData.clockMode) {
13993       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13994     } else if (movesPerSession > 0) {
13995       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13996     } else if (timeIncrement == 0) {
13997       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13998     } else {
13999       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14000     }
14001     return StrSave(buf);
14002 }
14003
14004 void
14005 SetGameInfo()
14006 {
14007     /* This routine is used only for certain modes */
14008     VariantClass v = gameInfo.variant;
14009     ChessMove r = GameUnfinished;
14010     char *p = NULL;
14011
14012     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14013         r = gameInfo.result;
14014         p = gameInfo.resultDetails;
14015         gameInfo.resultDetails = NULL;
14016     }
14017     ClearGameInfo(&gameInfo);
14018     gameInfo.variant = v;
14019
14020     switch (gameMode) {
14021       case MachinePlaysWhite:
14022         gameInfo.event = StrSave( appData.pgnEventHeader );
14023         gameInfo.site = StrSave(HostName());
14024         gameInfo.date = PGNDate();
14025         gameInfo.round = StrSave("-");
14026         gameInfo.white = StrSave(first.tidy);
14027         gameInfo.black = StrSave(UserName());
14028         gameInfo.timeControl = TimeControlTagValue();
14029         break;
14030
14031       case MachinePlaysBlack:
14032         gameInfo.event = StrSave( appData.pgnEventHeader );
14033         gameInfo.site = StrSave(HostName());
14034         gameInfo.date = PGNDate();
14035         gameInfo.round = StrSave("-");
14036         gameInfo.white = StrSave(UserName());
14037         gameInfo.black = StrSave(first.tidy);
14038         gameInfo.timeControl = TimeControlTagValue();
14039         break;
14040
14041       case TwoMachinesPlay:
14042         gameInfo.event = StrSave( appData.pgnEventHeader );
14043         gameInfo.site = StrSave(HostName());
14044         gameInfo.date = PGNDate();
14045         if (roundNr > 0) {
14046             char buf[MSG_SIZ];
14047             snprintf(buf, MSG_SIZ, "%d", roundNr);
14048             gameInfo.round = StrSave(buf);
14049         } else {
14050             gameInfo.round = StrSave("-");
14051         }
14052         if (first.twoMachinesColor[0] == 'w') {
14053             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14054             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14055         } else {
14056             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14057             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14058         }
14059         gameInfo.timeControl = TimeControlTagValue();
14060         break;
14061
14062       case EditGame:
14063         gameInfo.event = StrSave("Edited game");
14064         gameInfo.site = StrSave(HostName());
14065         gameInfo.date = PGNDate();
14066         gameInfo.round = StrSave("-");
14067         gameInfo.white = StrSave("-");
14068         gameInfo.black = StrSave("-");
14069         gameInfo.result = r;
14070         gameInfo.resultDetails = p;
14071         break;
14072
14073       case EditPosition:
14074         gameInfo.event = StrSave("Edited position");
14075         gameInfo.site = StrSave(HostName());
14076         gameInfo.date = PGNDate();
14077         gameInfo.round = StrSave("-");
14078         gameInfo.white = StrSave("-");
14079         gameInfo.black = StrSave("-");
14080         break;
14081
14082       case IcsPlayingWhite:
14083       case IcsPlayingBlack:
14084       case IcsObserving:
14085       case IcsExamining:
14086         break;
14087
14088       case PlayFromGameFile:
14089         gameInfo.event = StrSave("Game from non-PGN file");
14090         gameInfo.site = StrSave(HostName());
14091         gameInfo.date = PGNDate();
14092         gameInfo.round = StrSave("-");
14093         gameInfo.white = StrSave("?");
14094         gameInfo.black = StrSave("?");
14095         break;
14096
14097       default:
14098         break;
14099     }
14100 }
14101
14102 void
14103 ReplaceComment(index, text)
14104      int index;
14105      char *text;
14106 {
14107     int len;
14108     char *p;
14109     float score;
14110
14111     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14112        pvInfoList[index-1].depth == len &&
14113        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14114        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14115     while (*text == '\n') text++;
14116     len = strlen(text);
14117     while (len > 0 && text[len - 1] == '\n') len--;
14118
14119     if (commentList[index] != NULL)
14120       free(commentList[index]);
14121
14122     if (len == 0) {
14123         commentList[index] = NULL;
14124         return;
14125     }
14126   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14127       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14128       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14129     commentList[index] = (char *) malloc(len + 2);
14130     strncpy(commentList[index], text, len);
14131     commentList[index][len] = '\n';
14132     commentList[index][len + 1] = NULLCHAR;
14133   } else {
14134     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14135     char *p;
14136     commentList[index] = (char *) malloc(len + 7);
14137     safeStrCpy(commentList[index], "{\n", 3);
14138     safeStrCpy(commentList[index]+2, text, len+1);
14139     commentList[index][len+2] = NULLCHAR;
14140     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14141     strcat(commentList[index], "\n}\n");
14142   }
14143 }
14144
14145 void
14146 CrushCRs(text)
14147      char *text;
14148 {
14149   char *p = text;
14150   char *q = text;
14151   char ch;
14152
14153   do {
14154     ch = *p++;
14155     if (ch == '\r') continue;
14156     *q++ = ch;
14157   } while (ch != '\0');
14158 }
14159
14160 void
14161 AppendComment(index, text, addBraces)
14162      int index;
14163      char *text;
14164      Boolean addBraces; // [HGM] braces: tells if we should add {}
14165 {
14166     int oldlen, len;
14167     char *old;
14168
14169 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14170     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14171
14172     CrushCRs(text);
14173     while (*text == '\n') text++;
14174     len = strlen(text);
14175     while (len > 0 && text[len - 1] == '\n') len--;
14176
14177     if (len == 0) return;
14178
14179     if (commentList[index] != NULL) {
14180         old = commentList[index];
14181         oldlen = strlen(old);
14182         while(commentList[index][oldlen-1] ==  '\n')
14183           commentList[index][--oldlen] = NULLCHAR;
14184         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14185         safeStrCpy(commentList[index], old, oldlen + len + 6);
14186         free(old);
14187         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14188         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14189           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14190           while (*text == '\n') { text++; len--; }
14191           commentList[index][--oldlen] = NULLCHAR;
14192       }
14193         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14194         else          strcat(commentList[index], "\n");
14195         strcat(commentList[index], text);
14196         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14197         else          strcat(commentList[index], "\n");
14198     } else {
14199         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14200         if(addBraces)
14201           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14202         else commentList[index][0] = NULLCHAR;
14203         strcat(commentList[index], text);
14204         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14205         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14206     }
14207 }
14208
14209 static char * FindStr( char * text, char * sub_text )
14210 {
14211     char * result = strstr( text, sub_text );
14212
14213     if( result != NULL ) {
14214         result += strlen( sub_text );
14215     }
14216
14217     return result;
14218 }
14219
14220 /* [AS] Try to extract PV info from PGN comment */
14221 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14222 char *GetInfoFromComment( int index, char * text )
14223 {
14224     char * sep = text, *p;
14225
14226     if( text != NULL && index > 0 ) {
14227         int score = 0;
14228         int depth = 0;
14229         int time = -1, sec = 0, deci;
14230         char * s_eval = FindStr( text, "[%eval " );
14231         char * s_emt = FindStr( text, "[%emt " );
14232
14233         if( s_eval != NULL || s_emt != NULL ) {
14234             /* New style */
14235             char delim;
14236
14237             if( s_eval != NULL ) {
14238                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14239                     return text;
14240                 }
14241
14242                 if( delim != ']' ) {
14243                     return text;
14244                 }
14245             }
14246
14247             if( s_emt != NULL ) {
14248             }
14249                 return text;
14250         }
14251         else {
14252             /* We expect something like: [+|-]nnn.nn/dd */
14253             int score_lo = 0;
14254
14255             if(*text != '{') return text; // [HGM] braces: must be normal comment
14256
14257             sep = strchr( text, '/' );
14258             if( sep == NULL || sep < (text+4) ) {
14259                 return text;
14260             }
14261
14262             p = text;
14263             if(p[1] == '(') { // comment starts with PV
14264                p = strchr(p, ')'); // locate end of PV
14265                if(p == NULL || sep < p+5) return text;
14266                // at this point we have something like "{(.*) +0.23/6 ..."
14267                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14268                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14269                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14270             }
14271             time = -1; sec = -1; deci = -1;
14272             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14273                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14274                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14275                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14276                 return text;
14277             }
14278
14279             if( score_lo < 0 || score_lo >= 100 ) {
14280                 return text;
14281             }
14282
14283             if(sec >= 0) time = 600*time + 10*sec; else
14284             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14285
14286             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14287
14288             /* [HGM] PV time: now locate end of PV info */
14289             while( *++sep >= '0' && *sep <= '9'); // strip depth
14290             if(time >= 0)
14291             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14292             if(sec >= 0)
14293             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14294             if(deci >= 0)
14295             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14296             while(*sep == ' ') sep++;
14297         }
14298
14299         if( depth <= 0 ) {
14300             return text;
14301         }
14302
14303         if( time < 0 ) {
14304             time = -1;
14305         }
14306
14307         pvInfoList[index-1].depth = depth;
14308         pvInfoList[index-1].score = score;
14309         pvInfoList[index-1].time  = 10*time; // centi-sec
14310         if(*sep == '}') *sep = 0; else *--sep = '{';
14311         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14312     }
14313     return sep;
14314 }
14315
14316 void
14317 SendToProgram(message, cps)
14318      char *message;
14319      ChessProgramState *cps;
14320 {
14321     int count, outCount, error;
14322     char buf[MSG_SIZ];
14323
14324     if (cps->pr == NULL) return;
14325     Attention(cps);
14326
14327     if (appData.debugMode) {
14328         TimeMark now;
14329         GetTimeMark(&now);
14330         fprintf(debugFP, "%ld >%-6s: %s",
14331                 SubtractTimeMarks(&now, &programStartTime),
14332                 cps->which, message);
14333     }
14334
14335     count = strlen(message);
14336     outCount = OutputToProcess(cps->pr, message, count, &error);
14337     if (outCount < count && !exiting
14338                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14339       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14340       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14341         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14342             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14343                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14344                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14345                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14346             } else {
14347                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14348                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14349                 gameInfo.result = res;
14350             }
14351             gameInfo.resultDetails = StrSave(buf);
14352         }
14353         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14354         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14355     }
14356 }
14357
14358 void
14359 ReceiveFromProgram(isr, closure, message, count, error)
14360      InputSourceRef isr;
14361      VOIDSTAR closure;
14362      char *message;
14363      int count;
14364      int error;
14365 {
14366     char *end_str;
14367     char buf[MSG_SIZ];
14368     ChessProgramState *cps = (ChessProgramState *)closure;
14369
14370     if (isr != cps->isr) return; /* Killed intentionally */
14371     if (count <= 0) {
14372         if (count == 0) {
14373             RemoveInputSource(cps->isr);
14374             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14375             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14376                     _(cps->which), cps->program);
14377         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14378                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14379                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14380                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14381                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14382                 } else {
14383                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14384                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14385                     gameInfo.result = res;
14386                 }
14387                 gameInfo.resultDetails = StrSave(buf);
14388             }
14389             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14390             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14391         } else {
14392             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14393                     _(cps->which), cps->program);
14394             RemoveInputSource(cps->isr);
14395
14396             /* [AS] Program is misbehaving badly... kill it */
14397             if( count == -2 ) {
14398                 DestroyChildProcess( cps->pr, 9 );
14399                 cps->pr = NoProc;
14400             }
14401
14402             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14403         }
14404         return;
14405     }
14406
14407     if ((end_str = strchr(message, '\r')) != NULL)
14408       *end_str = NULLCHAR;
14409     if ((end_str = strchr(message, '\n')) != NULL)
14410       *end_str = NULLCHAR;
14411
14412     if (appData.debugMode) {
14413         TimeMark now; int print = 1;
14414         char *quote = ""; char c; int i;
14415
14416         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14417                 char start = message[0];
14418                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14419                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14420                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14421                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14422                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14423                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14424                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14425                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14426                    sscanf(message, "hint: %c", &c)!=1 && 
14427                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14428                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14429                     print = (appData.engineComments >= 2);
14430                 }
14431                 message[0] = start; // restore original message
14432         }
14433         if(print) {
14434                 GetTimeMark(&now);
14435                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14436                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14437                         quote,
14438                         message);
14439         }
14440     }
14441
14442     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14443     if (appData.icsEngineAnalyze) {
14444         if (strstr(message, "whisper") != NULL ||
14445              strstr(message, "kibitz") != NULL ||
14446             strstr(message, "tellics") != NULL) return;
14447     }
14448
14449     HandleMachineMove(message, cps);
14450 }
14451
14452
14453 void
14454 SendTimeControl(cps, mps, tc, inc, sd, st)
14455      ChessProgramState *cps;
14456      int mps, inc, sd, st;
14457      long tc;
14458 {
14459     char buf[MSG_SIZ];
14460     int seconds;
14461
14462     if( timeControl_2 > 0 ) {
14463         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14464             tc = timeControl_2;
14465         }
14466     }
14467     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14468     inc /= cps->timeOdds;
14469     st  /= cps->timeOdds;
14470
14471     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14472
14473     if (st > 0) {
14474       /* Set exact time per move, normally using st command */
14475       if (cps->stKludge) {
14476         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14477         seconds = st % 60;
14478         if (seconds == 0) {
14479           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14480         } else {
14481           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14482         }
14483       } else {
14484         snprintf(buf, MSG_SIZ, "st %d\n", st);
14485       }
14486     } else {
14487       /* Set conventional or incremental time control, using level command */
14488       if (seconds == 0) {
14489         /* Note old gnuchess bug -- minutes:seconds used to not work.
14490            Fixed in later versions, but still avoid :seconds
14491            when seconds is 0. */
14492         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14493       } else {
14494         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14495                  seconds, inc/1000.);
14496       }
14497     }
14498     SendToProgram(buf, cps);
14499
14500     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14501     /* Orthogonally, limit search to given depth */
14502     if (sd > 0) {
14503       if (cps->sdKludge) {
14504         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14505       } else {
14506         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14507       }
14508       SendToProgram(buf, cps);
14509     }
14510
14511     if(cps->nps >= 0) { /* [HGM] nps */
14512         if(cps->supportsNPS == FALSE)
14513           cps->nps = -1; // don't use if engine explicitly says not supported!
14514         else {
14515           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14516           SendToProgram(buf, cps);
14517         }
14518     }
14519 }
14520
14521 ChessProgramState *WhitePlayer()
14522 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14523 {
14524     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14525        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14526         return &second;
14527     return &first;
14528 }
14529
14530 void
14531 SendTimeRemaining(cps, machineWhite)
14532      ChessProgramState *cps;
14533      int /*boolean*/ machineWhite;
14534 {
14535     char message[MSG_SIZ];
14536     long time, otime;
14537
14538     /* Note: this routine must be called when the clocks are stopped
14539        or when they have *just* been set or switched; otherwise
14540        it will be off by the time since the current tick started.
14541     */
14542     if (machineWhite) {
14543         time = whiteTimeRemaining / 10;
14544         otime = blackTimeRemaining / 10;
14545     } else {
14546         time = blackTimeRemaining / 10;
14547         otime = whiteTimeRemaining / 10;
14548     }
14549     /* [HGM] translate opponent's time by time-odds factor */
14550     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14551     if (appData.debugMode) {
14552         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14553     }
14554
14555     if (time <= 0) time = 1;
14556     if (otime <= 0) otime = 1;
14557
14558     snprintf(message, MSG_SIZ, "time %ld\n", time);
14559     SendToProgram(message, cps);
14560
14561     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14562     SendToProgram(message, cps);
14563 }
14564
14565 int
14566 BoolFeature(p, name, loc, cps)
14567      char **p;
14568      char *name;
14569      int *loc;
14570      ChessProgramState *cps;
14571 {
14572   char buf[MSG_SIZ];
14573   int len = strlen(name);
14574   int val;
14575
14576   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14577     (*p) += len + 1;
14578     sscanf(*p, "%d", &val);
14579     *loc = (val != 0);
14580     while (**p && **p != ' ')
14581       (*p)++;
14582     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14583     SendToProgram(buf, cps);
14584     return TRUE;
14585   }
14586   return FALSE;
14587 }
14588
14589 int
14590 IntFeature(p, name, loc, cps)
14591      char **p;
14592      char *name;
14593      int *loc;
14594      ChessProgramState *cps;
14595 {
14596   char buf[MSG_SIZ];
14597   int len = strlen(name);
14598   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14599     (*p) += len + 1;
14600     sscanf(*p, "%d", loc);
14601     while (**p && **p != ' ') (*p)++;
14602     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14603     SendToProgram(buf, cps);
14604     return TRUE;
14605   }
14606   return FALSE;
14607 }
14608
14609 int
14610 StringFeature(p, name, loc, cps)
14611      char **p;
14612      char *name;
14613      char loc[];
14614      ChessProgramState *cps;
14615 {
14616   char buf[MSG_SIZ];
14617   int len = strlen(name);
14618   if (strncmp((*p), name, len) == 0
14619       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14620     (*p) += len + 2;
14621     sscanf(*p, "%[^\"]", loc);
14622     while (**p && **p != '\"') (*p)++;
14623     if (**p == '\"') (*p)++;
14624     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14625     SendToProgram(buf, cps);
14626     return TRUE;
14627   }
14628   return FALSE;
14629 }
14630
14631 int
14632 ParseOption(Option *opt, ChessProgramState *cps)
14633 // [HGM] options: process the string that defines an engine option, and determine
14634 // name, type, default value, and allowed value range
14635 {
14636         char *p, *q, buf[MSG_SIZ];
14637         int n, min = (-1)<<31, max = 1<<31, def;
14638
14639         if(p = strstr(opt->name, " -spin ")) {
14640             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14641             if(max < min) max = min; // enforce consistency
14642             if(def < min) def = min;
14643             if(def > max) def = max;
14644             opt->value = def;
14645             opt->min = min;
14646             opt->max = max;
14647             opt->type = Spin;
14648         } else if((p = strstr(opt->name, " -slider "))) {
14649             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14650             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14651             if(max < min) max = min; // enforce consistency
14652             if(def < min) def = min;
14653             if(def > max) def = max;
14654             opt->value = def;
14655             opt->min = min;
14656             opt->max = max;
14657             opt->type = Spin; // Slider;
14658         } else if((p = strstr(opt->name, " -string "))) {
14659             opt->textValue = p+9;
14660             opt->type = TextBox;
14661         } else if((p = strstr(opt->name, " -file "))) {
14662             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14663             opt->textValue = p+7;
14664             opt->type = FileName; // FileName;
14665         } else if((p = strstr(opt->name, " -path "))) {
14666             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14667             opt->textValue = p+7;
14668             opt->type = PathName; // PathName;
14669         } else if(p = strstr(opt->name, " -check ")) {
14670             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14671             opt->value = (def != 0);
14672             opt->type = CheckBox;
14673         } else if(p = strstr(opt->name, " -combo ")) {
14674             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14675             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14676             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14677             opt->value = n = 0;
14678             while(q = StrStr(q, " /// ")) {
14679                 n++; *q = 0;    // count choices, and null-terminate each of them
14680                 q += 5;
14681                 if(*q == '*') { // remember default, which is marked with * prefix
14682                     q++;
14683                     opt->value = n;
14684                 }
14685                 cps->comboList[cps->comboCnt++] = q;
14686             }
14687             cps->comboList[cps->comboCnt++] = NULL;
14688             opt->max = n + 1;
14689             opt->type = ComboBox;
14690         } else if(p = strstr(opt->name, " -button")) {
14691             opt->type = Button;
14692         } else if(p = strstr(opt->name, " -save")) {
14693             opt->type = SaveButton;
14694         } else return FALSE;
14695         *p = 0; // terminate option name
14696         // now look if the command-line options define a setting for this engine option.
14697         if(cps->optionSettings && cps->optionSettings[0])
14698             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14699         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14700           snprintf(buf, MSG_SIZ, "option %s", p);
14701                 if(p = strstr(buf, ",")) *p = 0;
14702                 if(q = strchr(buf, '=')) switch(opt->type) {
14703                     case ComboBox:
14704                         for(n=0; n<opt->max; n++)
14705                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14706                         break;
14707                     case TextBox:
14708                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14709                         break;
14710                     case Spin:
14711                     case CheckBox:
14712                         opt->value = atoi(q+1);
14713                     default:
14714                         break;
14715                 }
14716                 strcat(buf, "\n");
14717                 SendToProgram(buf, cps);
14718         }
14719         return TRUE;
14720 }
14721
14722 void
14723 FeatureDone(cps, val)
14724      ChessProgramState* cps;
14725      int val;
14726 {
14727   DelayedEventCallback cb = GetDelayedEvent();
14728   if ((cb == InitBackEnd3 && cps == &first) ||
14729       (cb == SettingsMenuIfReady && cps == &second) ||
14730       (cb == LoadEngine) ||
14731       (cb == TwoMachinesEventIfReady)) {
14732     CancelDelayedEvent();
14733     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14734   }
14735   cps->initDone = val;
14736 }
14737
14738 /* Parse feature command from engine */
14739 void
14740 ParseFeatures(args, cps)
14741      char* args;
14742      ChessProgramState *cps;
14743 {
14744   char *p = args;
14745   char *q;
14746   int val;
14747   char buf[MSG_SIZ];
14748
14749   for (;;) {
14750     while (*p == ' ') p++;
14751     if (*p == NULLCHAR) return;
14752
14753     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14754     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14755     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14756     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14757     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14758     if (BoolFeature(&p, "reuse", &val, cps)) {
14759       /* Engine can disable reuse, but can't enable it if user said no */
14760       if (!val) cps->reuse = FALSE;
14761       continue;
14762     }
14763     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14764     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14765       if (gameMode == TwoMachinesPlay) {
14766         DisplayTwoMachinesTitle();
14767       } else {
14768         DisplayTitle("");
14769       }
14770       continue;
14771     }
14772     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14773     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14774     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14775     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14776     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14777     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14778     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14779     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14780     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14781     if (IntFeature(&p, "done", &val, cps)) {
14782       FeatureDone(cps, val);
14783       continue;
14784     }
14785     /* Added by Tord: */
14786     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14787     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14788     /* End of additions by Tord */
14789
14790     /* [HGM] added features: */
14791     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14792     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14793     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14794     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14795     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14796     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14797     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14798         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14799           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14800             SendToProgram(buf, cps);
14801             continue;
14802         }
14803         if(cps->nrOptions >= MAX_OPTIONS) {
14804             cps->nrOptions--;
14805             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14806             DisplayError(buf, 0);
14807         }
14808         continue;
14809     }
14810     /* End of additions by HGM */
14811
14812     /* unknown feature: complain and skip */
14813     q = p;
14814     while (*q && *q != '=') q++;
14815     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14816     SendToProgram(buf, cps);
14817     p = q;
14818     if (*p == '=') {
14819       p++;
14820       if (*p == '\"') {
14821         p++;
14822         while (*p && *p != '\"') p++;
14823         if (*p == '\"') p++;
14824       } else {
14825         while (*p && *p != ' ') p++;
14826       }
14827     }
14828   }
14829
14830 }
14831
14832 void
14833 PeriodicUpdatesEvent(newState)
14834      int newState;
14835 {
14836     if (newState == appData.periodicUpdates)
14837       return;
14838
14839     appData.periodicUpdates=newState;
14840
14841     /* Display type changes, so update it now */
14842 //    DisplayAnalysis();
14843
14844     /* Get the ball rolling again... */
14845     if (newState) {
14846         AnalysisPeriodicEvent(1);
14847         StartAnalysisClock();
14848     }
14849 }
14850
14851 void
14852 PonderNextMoveEvent(newState)
14853      int newState;
14854 {
14855     if (newState == appData.ponderNextMove) return;
14856     if (gameMode == EditPosition) EditPositionDone(TRUE);
14857     if (newState) {
14858         SendToProgram("hard\n", &first);
14859         if (gameMode == TwoMachinesPlay) {
14860             SendToProgram("hard\n", &second);
14861         }
14862     } else {
14863         SendToProgram("easy\n", &first);
14864         thinkOutput[0] = NULLCHAR;
14865         if (gameMode == TwoMachinesPlay) {
14866             SendToProgram("easy\n", &second);
14867         }
14868     }
14869     appData.ponderNextMove = newState;
14870 }
14871
14872 void
14873 NewSettingEvent(option, feature, command, value)
14874      char *command;
14875      int option, value, *feature;
14876 {
14877     char buf[MSG_SIZ];
14878
14879     if (gameMode == EditPosition) EditPositionDone(TRUE);
14880     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14881     if(feature == NULL || *feature) SendToProgram(buf, &first);
14882     if (gameMode == TwoMachinesPlay) {
14883         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14884     }
14885 }
14886
14887 void
14888 ShowThinkingEvent()
14889 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14890 {
14891     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14892     int newState = appData.showThinking
14893         // [HGM] thinking: other features now need thinking output as well
14894         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14895
14896     if (oldState == newState) return;
14897     oldState = newState;
14898     if (gameMode == EditPosition) EditPositionDone(TRUE);
14899     if (oldState) {
14900         SendToProgram("post\n", &first);
14901         if (gameMode == TwoMachinesPlay) {
14902             SendToProgram("post\n", &second);
14903         }
14904     } else {
14905         SendToProgram("nopost\n", &first);
14906         thinkOutput[0] = NULLCHAR;
14907         if (gameMode == TwoMachinesPlay) {
14908             SendToProgram("nopost\n", &second);
14909         }
14910     }
14911 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14912 }
14913
14914 void
14915 AskQuestionEvent(title, question, replyPrefix, which)
14916      char *title; char *question; char *replyPrefix; char *which;
14917 {
14918   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14919   if (pr == NoProc) return;
14920   AskQuestion(title, question, replyPrefix, pr);
14921 }
14922
14923 void
14924 TypeInEvent(char firstChar)
14925 {
14926     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14927         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14928         gameMode == AnalyzeMode || gameMode == EditGame || \r
14929         gameMode == EditPosition || gameMode == IcsExamining ||\r
14930         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14931         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14932                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14933                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14934         gameMode == Training) PopUpMoveDialog(firstChar);
14935 }
14936
14937 void
14938 TypeInDoneEvent(char *move)
14939 {
14940         Board board;
14941         int n, fromX, fromY, toX, toY;
14942         char promoChar;
14943         ChessMove moveType;\r
14944
14945         // [HGM] FENedit\r
14946         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14947                 EditPositionPasteFEN(move);\r
14948                 return;\r
14949         }\r
14950         // [HGM] movenum: allow move number to be typed in any mode\r
14951         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14952           ToNrEvent(2*n-1);\r
14953           return;\r
14954         }\r
14955
14956       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14957         gameMode != Training) {\r
14958         DisplayMoveError(_("Displayed move is not current"));\r
14959       } else {\r
14960         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14961           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14962         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14963         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14964           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14965           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14966         } else {\r
14967           DisplayMoveError(_("Could not parse move"));\r
14968         }
14969       }\r
14970 }\r
14971
14972 void
14973 DisplayMove(moveNumber)
14974      int moveNumber;
14975 {
14976     char message[MSG_SIZ];
14977     char res[MSG_SIZ];
14978     char cpThinkOutput[MSG_SIZ];
14979
14980     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14981
14982     if (moveNumber == forwardMostMove - 1 ||
14983         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14984
14985         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14986
14987         if (strchr(cpThinkOutput, '\n')) {
14988             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14989         }
14990     } else {
14991         *cpThinkOutput = NULLCHAR;
14992     }
14993
14994     /* [AS] Hide thinking from human user */
14995     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14996         *cpThinkOutput = NULLCHAR;
14997         if( thinkOutput[0] != NULLCHAR ) {
14998             int i;
14999
15000             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15001                 cpThinkOutput[i] = '.';
15002             }
15003             cpThinkOutput[i] = NULLCHAR;
15004             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15005         }
15006     }
15007
15008     if (moveNumber == forwardMostMove - 1 &&
15009         gameInfo.resultDetails != NULL) {
15010         if (gameInfo.resultDetails[0] == NULLCHAR) {
15011           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15012         } else {
15013           snprintf(res, MSG_SIZ, " {%s} %s",
15014                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15015         }
15016     } else {
15017         res[0] = NULLCHAR;
15018     }
15019
15020     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15021         DisplayMessage(res, cpThinkOutput);
15022     } else {
15023       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15024                 WhiteOnMove(moveNumber) ? " " : ".. ",
15025                 parseList[moveNumber], res);
15026         DisplayMessage(message, cpThinkOutput);
15027     }
15028 }
15029
15030 void
15031 DisplayComment(moveNumber, text)
15032      int moveNumber;
15033      char *text;
15034 {
15035     char title[MSG_SIZ];
15036     char buf[8000]; // comment can be long!
15037     int score, depth;
15038
15039     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15040       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15041     } else {
15042       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15043               WhiteOnMove(moveNumber) ? " " : ".. ",
15044               parseList[moveNumber]);
15045     }
15046     // [HGM] PV info: display PV info together with (or as) comment
15047     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15048       if(text == NULL) text = "";
15049       score = pvInfoList[moveNumber].score;
15050       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15051               depth, (pvInfoList[moveNumber].time+50)/100, text);
15052       text = buf;
15053     }
15054     if (text != NULL && (appData.autoDisplayComment || commentUp))
15055         CommentPopUp(title, text);
15056 }
15057
15058 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15059  * might be busy thinking or pondering.  It can be omitted if your
15060  * gnuchess is configured to stop thinking immediately on any user
15061  * input.  However, that gnuchess feature depends on the FIONREAD
15062  * ioctl, which does not work properly on some flavors of Unix.
15063  */
15064 void
15065 Attention(cps)
15066      ChessProgramState *cps;
15067 {
15068 #if ATTENTION
15069     if (!cps->useSigint) return;
15070     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15071     switch (gameMode) {
15072       case MachinePlaysWhite:
15073       case MachinePlaysBlack:
15074       case TwoMachinesPlay:
15075       case IcsPlayingWhite:
15076       case IcsPlayingBlack:
15077       case AnalyzeMode:
15078       case AnalyzeFile:
15079         /* Skip if we know it isn't thinking */
15080         if (!cps->maybeThinking) return;
15081         if (appData.debugMode)
15082           fprintf(debugFP, "Interrupting %s\n", cps->which);
15083         InterruptChildProcess(cps->pr);
15084         cps->maybeThinking = FALSE;
15085         break;
15086       default:
15087         break;
15088     }
15089 #endif /*ATTENTION*/
15090 }
15091
15092 int
15093 CheckFlags()
15094 {
15095     if (whiteTimeRemaining <= 0) {
15096         if (!whiteFlag) {
15097             whiteFlag = TRUE;
15098             if (appData.icsActive) {
15099                 if (appData.autoCallFlag &&
15100                     gameMode == IcsPlayingBlack && !blackFlag) {
15101                   SendToICS(ics_prefix);
15102                   SendToICS("flag\n");
15103                 }
15104             } else {
15105                 if (blackFlag) {
15106                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15107                 } else {
15108                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15109                     if (appData.autoCallFlag) {
15110                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15111                         return TRUE;
15112                     }
15113                 }
15114             }
15115         }
15116     }
15117     if (blackTimeRemaining <= 0) {
15118         if (!blackFlag) {
15119             blackFlag = TRUE;
15120             if (appData.icsActive) {
15121                 if (appData.autoCallFlag &&
15122                     gameMode == IcsPlayingWhite && !whiteFlag) {
15123                   SendToICS(ics_prefix);
15124                   SendToICS("flag\n");
15125                 }
15126             } else {
15127                 if (whiteFlag) {
15128                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15129                 } else {
15130                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15131                     if (appData.autoCallFlag) {
15132                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15133                         return TRUE;
15134                     }
15135                 }
15136             }
15137         }
15138     }
15139     return FALSE;
15140 }
15141
15142 void
15143 CheckTimeControl()
15144 {
15145     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15146         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15147
15148     /*
15149      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15150      */
15151     if ( !WhiteOnMove(forwardMostMove) ) {
15152         /* White made time control */
15153         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15154         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15155         /* [HGM] time odds: correct new time quota for time odds! */
15156                                             / WhitePlayer()->timeOdds;
15157         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15158     } else {
15159         lastBlack -= blackTimeRemaining;
15160         /* Black made time control */
15161         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15162                                             / WhitePlayer()->other->timeOdds;
15163         lastWhite = whiteTimeRemaining;
15164     }
15165 }
15166
15167 void
15168 DisplayBothClocks()
15169 {
15170     int wom = gameMode == EditPosition ?
15171       !blackPlaysFirst : WhiteOnMove(currentMove);
15172     DisplayWhiteClock(whiteTimeRemaining, wom);
15173     DisplayBlackClock(blackTimeRemaining, !wom);
15174 }
15175
15176
15177 /* Timekeeping seems to be a portability nightmare.  I think everyone
15178    has ftime(), but I'm really not sure, so I'm including some ifdefs
15179    to use other calls if you don't.  Clocks will be less accurate if
15180    you have neither ftime nor gettimeofday.
15181 */
15182
15183 /* VS 2008 requires the #include outside of the function */
15184 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15185 #include <sys/timeb.h>
15186 #endif
15187
15188 /* Get the current time as a TimeMark */
15189 void
15190 GetTimeMark(tm)
15191      TimeMark *tm;
15192 {
15193 #if HAVE_GETTIMEOFDAY
15194
15195     struct timeval timeVal;
15196     struct timezone timeZone;
15197
15198     gettimeofday(&timeVal, &timeZone);
15199     tm->sec = (long) timeVal.tv_sec;
15200     tm->ms = (int) (timeVal.tv_usec / 1000L);
15201
15202 #else /*!HAVE_GETTIMEOFDAY*/
15203 #if HAVE_FTIME
15204
15205 // include <sys/timeb.h> / moved to just above start of function
15206     struct timeb timeB;
15207
15208     ftime(&timeB);
15209     tm->sec = (long) timeB.time;
15210     tm->ms = (int) timeB.millitm;
15211
15212 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15213     tm->sec = (long) time(NULL);
15214     tm->ms = 0;
15215 #endif
15216 #endif
15217 }
15218
15219 /* Return the difference in milliseconds between two
15220    time marks.  We assume the difference will fit in a long!
15221 */
15222 long
15223 SubtractTimeMarks(tm2, tm1)
15224      TimeMark *tm2, *tm1;
15225 {
15226     return 1000L*(tm2->sec - tm1->sec) +
15227            (long) (tm2->ms - tm1->ms);
15228 }
15229
15230
15231 /*
15232  * Code to manage the game clocks.
15233  *
15234  * In tournament play, black starts the clock and then white makes a move.
15235  * We give the human user a slight advantage if he is playing white---the
15236  * clocks don't run until he makes his first move, so it takes zero time.
15237  * Also, we don't account for network lag, so we could get out of sync
15238  * with GNU Chess's clock -- but then, referees are always right.
15239  */
15240
15241 static TimeMark tickStartTM;
15242 static long intendedTickLength;
15243
15244 long
15245 NextTickLength(timeRemaining)
15246      long timeRemaining;
15247 {
15248     long nominalTickLength, nextTickLength;
15249
15250     if (timeRemaining > 0L && timeRemaining <= 10000L)
15251       nominalTickLength = 100L;
15252     else
15253       nominalTickLength = 1000L;
15254     nextTickLength = timeRemaining % nominalTickLength;
15255     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15256
15257     return nextTickLength;
15258 }
15259
15260 /* Adjust clock one minute up or down */
15261 void
15262 AdjustClock(Boolean which, int dir)
15263 {
15264     if(which) blackTimeRemaining += 60000*dir;
15265     else      whiteTimeRemaining += 60000*dir;
15266     DisplayBothClocks();
15267 }
15268
15269 /* Stop clocks and reset to a fresh time control */
15270 void
15271 ResetClocks()
15272 {
15273     (void) StopClockTimer();
15274     if (appData.icsActive) {
15275         whiteTimeRemaining = blackTimeRemaining = 0;
15276     } else if (searchTime) {
15277         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15278         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15279     } else { /* [HGM] correct new time quote for time odds */
15280         whiteTC = blackTC = fullTimeControlString;
15281         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15282         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15283     }
15284     if (whiteFlag || blackFlag) {
15285         DisplayTitle("");
15286         whiteFlag = blackFlag = FALSE;
15287     }
15288     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15289     DisplayBothClocks();
15290 }
15291
15292 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15293
15294 /* Decrement running clock by amount of time that has passed */
15295 void
15296 DecrementClocks()
15297 {
15298     long timeRemaining;
15299     long lastTickLength, fudge;
15300     TimeMark now;
15301
15302     if (!appData.clockMode) return;
15303     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15304
15305     GetTimeMark(&now);
15306
15307     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15308
15309     /* Fudge if we woke up a little too soon */
15310     fudge = intendedTickLength - lastTickLength;
15311     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15312
15313     if (WhiteOnMove(forwardMostMove)) {
15314         if(whiteNPS >= 0) lastTickLength = 0;
15315         timeRemaining = whiteTimeRemaining -= lastTickLength;
15316         if(timeRemaining < 0 && !appData.icsActive) {
15317             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15318             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15319                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15320                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15321             }
15322         }
15323         DisplayWhiteClock(whiteTimeRemaining - fudge,
15324                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15325     } else {
15326         if(blackNPS >= 0) lastTickLength = 0;
15327         timeRemaining = blackTimeRemaining -= lastTickLength;
15328         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15329             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15330             if(suddenDeath) {
15331                 blackStartMove = forwardMostMove;
15332                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15333             }
15334         }
15335         DisplayBlackClock(blackTimeRemaining - fudge,
15336                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15337     }
15338     if (CheckFlags()) return;
15339
15340     tickStartTM = now;
15341     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15342     StartClockTimer(intendedTickLength);
15343
15344     /* if the time remaining has fallen below the alarm threshold, sound the
15345      * alarm. if the alarm has sounded and (due to a takeback or time control
15346      * with increment) the time remaining has increased to a level above the
15347      * threshold, reset the alarm so it can sound again.
15348      */
15349
15350     if (appData.icsActive && appData.icsAlarm) {
15351
15352         /* make sure we are dealing with the user's clock */
15353         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15354                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15355            )) return;
15356
15357         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15358             alarmSounded = FALSE;
15359         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15360             PlayAlarmSound();
15361             alarmSounded = TRUE;
15362         }
15363     }
15364 }
15365
15366
15367 /* A player has just moved, so stop the previously running
15368    clock and (if in clock mode) start the other one.
15369    We redisplay both clocks in case we're in ICS mode, because
15370    ICS gives us an update to both clocks after every move.
15371    Note that this routine is called *after* forwardMostMove
15372    is updated, so the last fractional tick must be subtracted
15373    from the color that is *not* on move now.
15374 */
15375 void
15376 SwitchClocks(int newMoveNr)
15377 {
15378     long lastTickLength;
15379     TimeMark now;
15380     int flagged = FALSE;
15381
15382     GetTimeMark(&now);
15383
15384     if (StopClockTimer() && appData.clockMode) {
15385         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15386         if (!WhiteOnMove(forwardMostMove)) {
15387             if(blackNPS >= 0) lastTickLength = 0;
15388             blackTimeRemaining -= lastTickLength;
15389            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15390 //         if(pvInfoList[forwardMostMove].time == -1)
15391                  pvInfoList[forwardMostMove].time =               // use GUI time
15392                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15393         } else {
15394            if(whiteNPS >= 0) lastTickLength = 0;
15395            whiteTimeRemaining -= lastTickLength;
15396            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15397 //         if(pvInfoList[forwardMostMove].time == -1)
15398                  pvInfoList[forwardMostMove].time =
15399                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15400         }
15401         flagged = CheckFlags();
15402     }
15403     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15404     CheckTimeControl();
15405
15406     if (flagged || !appData.clockMode) return;
15407
15408     switch (gameMode) {
15409       case MachinePlaysBlack:
15410       case MachinePlaysWhite:
15411       case BeginningOfGame:
15412         if (pausing) return;
15413         break;
15414
15415       case EditGame:
15416       case PlayFromGameFile:
15417       case IcsExamining:
15418         return;
15419
15420       default:
15421         break;
15422     }
15423
15424     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15425         if(WhiteOnMove(forwardMostMove))
15426              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15427         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15428     }
15429
15430     tickStartTM = now;
15431     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15432       whiteTimeRemaining : blackTimeRemaining);
15433     StartClockTimer(intendedTickLength);
15434 }
15435
15436
15437 /* Stop both clocks */
15438 void
15439 StopClocks()
15440 {
15441     long lastTickLength;
15442     TimeMark now;
15443
15444     if (!StopClockTimer()) return;
15445     if (!appData.clockMode) return;
15446
15447     GetTimeMark(&now);
15448
15449     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15450     if (WhiteOnMove(forwardMostMove)) {
15451         if(whiteNPS >= 0) lastTickLength = 0;
15452         whiteTimeRemaining -= lastTickLength;
15453         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15454     } else {
15455         if(blackNPS >= 0) lastTickLength = 0;
15456         blackTimeRemaining -= lastTickLength;
15457         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15458     }
15459     CheckFlags();
15460 }
15461
15462 /* Start clock of player on move.  Time may have been reset, so
15463    if clock is already running, stop and restart it. */
15464 void
15465 StartClocks()
15466 {
15467     (void) StopClockTimer(); /* in case it was running already */
15468     DisplayBothClocks();
15469     if (CheckFlags()) return;
15470
15471     if (!appData.clockMode) return;
15472     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15473
15474     GetTimeMark(&tickStartTM);
15475     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15476       whiteTimeRemaining : blackTimeRemaining);
15477
15478    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15479     whiteNPS = blackNPS = -1;
15480     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15481        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15482         whiteNPS = first.nps;
15483     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15484        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15485         blackNPS = first.nps;
15486     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15487         whiteNPS = second.nps;
15488     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15489         blackNPS = second.nps;
15490     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15491
15492     StartClockTimer(intendedTickLength);
15493 }
15494
15495 char *
15496 TimeString(ms)
15497      long ms;
15498 {
15499     long second, minute, hour, day;
15500     char *sign = "";
15501     static char buf[32];
15502
15503     if (ms > 0 && ms <= 9900) {
15504       /* convert milliseconds to tenths, rounding up */
15505       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15506
15507       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15508       return buf;
15509     }
15510
15511     /* convert milliseconds to seconds, rounding up */
15512     /* use floating point to avoid strangeness of integer division
15513        with negative dividends on many machines */
15514     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15515
15516     if (second < 0) {
15517         sign = "-";
15518         second = -second;
15519     }
15520
15521     day = second / (60 * 60 * 24);
15522     second = second % (60 * 60 * 24);
15523     hour = second / (60 * 60);
15524     second = second % (60 * 60);
15525     minute = second / 60;
15526     second = second % 60;
15527
15528     if (day > 0)
15529       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15530               sign, day, hour, minute, second);
15531     else if (hour > 0)
15532       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15533     else
15534       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15535
15536     return buf;
15537 }
15538
15539
15540 /*
15541  * This is necessary because some C libraries aren't ANSI C compliant yet.
15542  */
15543 char *
15544 StrStr(string, match)
15545      char *string, *match;
15546 {
15547     int i, length;
15548
15549     length = strlen(match);
15550
15551     for (i = strlen(string) - length; i >= 0; i--, string++)
15552       if (!strncmp(match, string, length))
15553         return string;
15554
15555     return NULL;
15556 }
15557
15558 char *
15559 StrCaseStr(string, match)
15560      char *string, *match;
15561 {
15562     int i, j, length;
15563
15564     length = strlen(match);
15565
15566     for (i = strlen(string) - length; i >= 0; i--, string++) {
15567         for (j = 0; j < length; j++) {
15568             if (ToLower(match[j]) != ToLower(string[j]))
15569               break;
15570         }
15571         if (j == length) return string;
15572     }
15573
15574     return NULL;
15575 }
15576
15577 #ifndef _amigados
15578 int
15579 StrCaseCmp(s1, s2)
15580      char *s1, *s2;
15581 {
15582     char c1, c2;
15583
15584     for (;;) {
15585         c1 = ToLower(*s1++);
15586         c2 = ToLower(*s2++);
15587         if (c1 > c2) return 1;
15588         if (c1 < c2) return -1;
15589         if (c1 == NULLCHAR) return 0;
15590     }
15591 }
15592
15593
15594 int
15595 ToLower(c)
15596      int c;
15597 {
15598     return isupper(c) ? tolower(c) : c;
15599 }
15600
15601
15602 int
15603 ToUpper(c)
15604      int c;
15605 {
15606     return islower(c) ? toupper(c) : c;
15607 }
15608 #endif /* !_amigados    */
15609
15610 char *
15611 StrSave(s)
15612      char *s;
15613 {
15614   char *ret;
15615
15616   if ((ret = (char *) malloc(strlen(s) + 1)))
15617     {
15618       safeStrCpy(ret, s, strlen(s)+1);
15619     }
15620   return ret;
15621 }
15622
15623 char *
15624 StrSavePtr(s, savePtr)
15625      char *s, **savePtr;
15626 {
15627     if (*savePtr) {
15628         free(*savePtr);
15629     }
15630     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15631       safeStrCpy(*savePtr, s, strlen(s)+1);
15632     }
15633     return(*savePtr);
15634 }
15635
15636 char *
15637 PGNDate()
15638 {
15639     time_t clock;
15640     struct tm *tm;
15641     char buf[MSG_SIZ];
15642
15643     clock = time((time_t *)NULL);
15644     tm = localtime(&clock);
15645     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15646             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15647     return StrSave(buf);
15648 }
15649
15650
15651 char *
15652 PositionToFEN(move, overrideCastling)
15653      int move;
15654      char *overrideCastling;
15655 {
15656     int i, j, fromX, fromY, toX, toY;
15657     int whiteToPlay;
15658     char buf[128];
15659     char *p, *q;
15660     int emptycount;
15661     ChessSquare piece;
15662
15663     whiteToPlay = (gameMode == EditPosition) ?
15664       !blackPlaysFirst : (move % 2 == 0);
15665     p = buf;
15666
15667     /* Piece placement data */
15668     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15669         emptycount = 0;
15670         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15671             if (boards[move][i][j] == EmptySquare) {
15672                 emptycount++;
15673             } else { ChessSquare piece = boards[move][i][j];
15674                 if (emptycount > 0) {
15675                     if(emptycount<10) /* [HGM] can be >= 10 */
15676                         *p++ = '0' + emptycount;
15677                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15678                     emptycount = 0;
15679                 }
15680                 if(PieceToChar(piece) == '+') {
15681                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15682                     *p++ = '+';
15683                     piece = (ChessSquare)(DEMOTED piece);
15684                 }
15685                 *p++ = PieceToChar(piece);
15686                 if(p[-1] == '~') {
15687                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15688                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15689                     *p++ = '~';
15690                 }
15691             }
15692         }
15693         if (emptycount > 0) {
15694             if(emptycount<10) /* [HGM] can be >= 10 */
15695                 *p++ = '0' + emptycount;
15696             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15697             emptycount = 0;
15698         }
15699         *p++ = '/';
15700     }
15701     *(p - 1) = ' ';
15702
15703     /* [HGM] print Crazyhouse or Shogi holdings */
15704     if( gameInfo.holdingsWidth ) {
15705         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15706         q = p;
15707         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15708             piece = boards[move][i][BOARD_WIDTH-1];
15709             if( piece != EmptySquare )
15710               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15711                   *p++ = PieceToChar(piece);
15712         }
15713         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15714             piece = boards[move][BOARD_HEIGHT-i-1][0];
15715             if( piece != EmptySquare )
15716               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15717                   *p++ = PieceToChar(piece);
15718         }
15719
15720         if( q == p ) *p++ = '-';
15721         *p++ = ']';
15722         *p++ = ' ';
15723     }
15724
15725     /* Active color */
15726     *p++ = whiteToPlay ? 'w' : 'b';
15727     *p++ = ' ';
15728
15729   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15730     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15731   } else {
15732   if(nrCastlingRights) {
15733      q = p;
15734      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15735        /* [HGM] write directly from rights */
15736            if(boards[move][CASTLING][2] != NoRights &&
15737               boards[move][CASTLING][0] != NoRights   )
15738                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15739            if(boards[move][CASTLING][2] != NoRights &&
15740               boards[move][CASTLING][1] != NoRights   )
15741                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15742            if(boards[move][CASTLING][5] != NoRights &&
15743               boards[move][CASTLING][3] != NoRights   )
15744                 *p++ = boards[move][CASTLING][3] + AAA;
15745            if(boards[move][CASTLING][5] != NoRights &&
15746               boards[move][CASTLING][4] != NoRights   )
15747                 *p++ = boards[move][CASTLING][4] + AAA;
15748      } else {
15749
15750         /* [HGM] write true castling rights */
15751         if( nrCastlingRights == 6 ) {
15752             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15753                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15754             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15755                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15756             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15757                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15758             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15759                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15760         }
15761      }
15762      if (q == p) *p++ = '-'; /* No castling rights */
15763      *p++ = ' ';
15764   }
15765
15766   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15767      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15768     /* En passant target square */
15769     if (move > backwardMostMove) {
15770         fromX = moveList[move - 1][0] - AAA;
15771         fromY = moveList[move - 1][1] - ONE;
15772         toX = moveList[move - 1][2] - AAA;
15773         toY = moveList[move - 1][3] - ONE;
15774         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15775             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15776             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15777             fromX == toX) {
15778             /* 2-square pawn move just happened */
15779             *p++ = toX + AAA;
15780             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15781         } else {
15782             *p++ = '-';
15783         }
15784     } else if(move == backwardMostMove) {
15785         // [HGM] perhaps we should always do it like this, and forget the above?
15786         if((signed char)boards[move][EP_STATUS] >= 0) {
15787             *p++ = boards[move][EP_STATUS] + AAA;
15788             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15789         } else {
15790             *p++ = '-';
15791         }
15792     } else {
15793         *p++ = '-';
15794     }
15795     *p++ = ' ';
15796   }
15797   }
15798
15799     /* [HGM] find reversible plies */
15800     {   int i = 0, j=move;
15801
15802         if (appData.debugMode) { int k;
15803             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15804             for(k=backwardMostMove; k<=forwardMostMove; k++)
15805                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15806
15807         }
15808
15809         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15810         if( j == backwardMostMove ) i += initialRulePlies;
15811         sprintf(p, "%d ", i);
15812         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15813     }
15814     /* Fullmove number */
15815     sprintf(p, "%d", (move / 2) + 1);
15816
15817     return StrSave(buf);
15818 }
15819
15820 Boolean
15821 ParseFEN(board, blackPlaysFirst, fen)
15822     Board board;
15823      int *blackPlaysFirst;
15824      char *fen;
15825 {
15826     int i, j;
15827     char *p, c;
15828     int emptycount;
15829     ChessSquare piece;
15830
15831     p = fen;
15832
15833     /* [HGM] by default clear Crazyhouse holdings, if present */
15834     if(gameInfo.holdingsWidth) {
15835        for(i=0; i<BOARD_HEIGHT; i++) {
15836            board[i][0]             = EmptySquare; /* black holdings */
15837            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15838            board[i][1]             = (ChessSquare) 0; /* black counts */
15839            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15840        }
15841     }
15842
15843     /* Piece placement data */
15844     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15845         j = 0;
15846         for (;;) {
15847             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15848                 if (*p == '/') p++;
15849                 emptycount = gameInfo.boardWidth - j;
15850                 while (emptycount--)
15851                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15852                 break;
15853 #if(BOARD_FILES >= 10)
15854             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15855                 p++; emptycount=10;
15856                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15857                 while (emptycount--)
15858                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15859 #endif
15860             } else if (isdigit(*p)) {
15861                 emptycount = *p++ - '0';
15862                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15863                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15864                 while (emptycount--)
15865                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15866             } else if (*p == '+' || isalpha(*p)) {
15867                 if (j >= gameInfo.boardWidth) return FALSE;
15868                 if(*p=='+') {
15869                     piece = CharToPiece(*++p);
15870                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15871                     piece = (ChessSquare) (PROMOTED piece ); p++;
15872                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15873                 } else piece = CharToPiece(*p++);
15874
15875                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15876                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15877                     piece = (ChessSquare) (PROMOTED piece);
15878                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15879                     p++;
15880                 }
15881                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15882             } else {
15883                 return FALSE;
15884             }
15885         }
15886     }
15887     while (*p == '/' || *p == ' ') p++;
15888
15889     /* [HGM] look for Crazyhouse holdings here */
15890     while(*p==' ') p++;
15891     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15892         if(*p == '[') p++;
15893         if(*p == '-' ) p++; /* empty holdings */ else {
15894             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15895             /* if we would allow FEN reading to set board size, we would   */
15896             /* have to add holdings and shift the board read so far here   */
15897             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15898                 p++;
15899                 if((int) piece >= (int) BlackPawn ) {
15900                     i = (int)piece - (int)BlackPawn;
15901                     i = PieceToNumber((ChessSquare)i);
15902                     if( i >= gameInfo.holdingsSize ) return FALSE;
15903                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15904                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15905                 } else {
15906                     i = (int)piece - (int)WhitePawn;
15907                     i = PieceToNumber((ChessSquare)i);
15908                     if( i >= gameInfo.holdingsSize ) return FALSE;
15909                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15910                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15911                 }
15912             }
15913         }
15914         if(*p == ']') p++;
15915     }
15916
15917     while(*p == ' ') p++;
15918
15919     /* Active color */
15920     c = *p++;
15921     if(appData.colorNickNames) {
15922       if( c == appData.colorNickNames[0] ) c = 'w'; else
15923       if( c == appData.colorNickNames[1] ) c = 'b';
15924     }
15925     switch (c) {
15926       case 'w':
15927         *blackPlaysFirst = FALSE;
15928         break;
15929       case 'b':
15930         *blackPlaysFirst = TRUE;
15931         break;
15932       default:
15933         return FALSE;
15934     }
15935
15936     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15937     /* return the extra info in global variiables             */
15938
15939     /* set defaults in case FEN is incomplete */
15940     board[EP_STATUS] = EP_UNKNOWN;
15941     for(i=0; i<nrCastlingRights; i++ ) {
15942         board[CASTLING][i] =
15943             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15944     }   /* assume possible unless obviously impossible */
15945     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15946     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15947     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15948                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15949     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15950     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15951     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15952                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15953     FENrulePlies = 0;
15954
15955     while(*p==' ') p++;
15956     if(nrCastlingRights) {
15957       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15958           /* castling indicator present, so default becomes no castlings */
15959           for(i=0; i<nrCastlingRights; i++ ) {
15960                  board[CASTLING][i] = NoRights;
15961           }
15962       }
15963       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15964              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15965              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15966              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15967         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15968
15969         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15970             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15971             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15972         }
15973         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15974             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15975         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15976                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15977         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15978                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15979         switch(c) {
15980           case'K':
15981               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15982               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15983               board[CASTLING][2] = whiteKingFile;
15984               break;
15985           case'Q':
15986               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15987               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15988               board[CASTLING][2] = whiteKingFile;
15989               break;
15990           case'k':
15991               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15992               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15993               board[CASTLING][5] = blackKingFile;
15994               break;
15995           case'q':
15996               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15997               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15998               board[CASTLING][5] = blackKingFile;
15999           case '-':
16000               break;
16001           default: /* FRC castlings */
16002               if(c >= 'a') { /* black rights */
16003                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16004                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16005                   if(i == BOARD_RGHT) break;
16006                   board[CASTLING][5] = i;
16007                   c -= AAA;
16008                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16009                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16010                   if(c > i)
16011                       board[CASTLING][3] = c;
16012                   else
16013                       board[CASTLING][4] = c;
16014               } else { /* white rights */
16015                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16016                     if(board[0][i] == WhiteKing) break;
16017                   if(i == BOARD_RGHT) break;
16018                   board[CASTLING][2] = i;
16019                   c -= AAA - 'a' + 'A';
16020                   if(board[0][c] >= WhiteKing) break;
16021                   if(c > i)
16022                       board[CASTLING][0] = c;
16023                   else
16024                       board[CASTLING][1] = c;
16025               }
16026         }
16027       }
16028       for(i=0; i<nrCastlingRights; i++)
16029         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16030     if (appData.debugMode) {
16031         fprintf(debugFP, "FEN castling rights:");
16032         for(i=0; i<nrCastlingRights; i++)
16033         fprintf(debugFP, " %d", board[CASTLING][i]);
16034         fprintf(debugFP, "\n");
16035     }
16036
16037       while(*p==' ') p++;
16038     }
16039
16040     /* read e.p. field in games that know e.p. capture */
16041     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16042        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16043       if(*p=='-') {
16044         p++; board[EP_STATUS] = EP_NONE;
16045       } else {
16046          char c = *p++ - AAA;
16047
16048          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16049          if(*p >= '0' && *p <='9') p++;
16050          board[EP_STATUS] = c;
16051       }
16052     }
16053
16054
16055     if(sscanf(p, "%d", &i) == 1) {
16056         FENrulePlies = i; /* 50-move ply counter */
16057         /* (The move number is still ignored)    */
16058     }
16059
16060     return TRUE;
16061 }
16062
16063 void
16064 EditPositionPasteFEN(char *fen)
16065 {
16066   if (fen != NULL) {
16067     Board initial_position;
16068
16069     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16070       DisplayError(_("Bad FEN position in clipboard"), 0);
16071       return ;
16072     } else {
16073       int savedBlackPlaysFirst = blackPlaysFirst;
16074       EditPositionEvent();
16075       blackPlaysFirst = savedBlackPlaysFirst;
16076       CopyBoard(boards[0], initial_position);
16077       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16078       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16079       DisplayBothClocks();
16080       DrawPosition(FALSE, boards[currentMove]);
16081     }
16082   }
16083 }
16084
16085 static char cseq[12] = "\\   ";
16086
16087 Boolean set_cont_sequence(char *new_seq)
16088 {
16089     int len;
16090     Boolean ret;
16091
16092     // handle bad attempts to set the sequence
16093         if (!new_seq)
16094                 return 0; // acceptable error - no debug
16095
16096     len = strlen(new_seq);
16097     ret = (len > 0) && (len < sizeof(cseq));
16098     if (ret)
16099       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16100     else if (appData.debugMode)
16101       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16102     return ret;
16103 }
16104
16105 /*
16106     reformat a source message so words don't cross the width boundary.  internal
16107     newlines are not removed.  returns the wrapped size (no null character unless
16108     included in source message).  If dest is NULL, only calculate the size required
16109     for the dest buffer.  lp argument indicats line position upon entry, and it's
16110     passed back upon exit.
16111 */
16112 int wrap(char *dest, char *src, int count, int width, int *lp)
16113 {
16114     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16115
16116     cseq_len = strlen(cseq);
16117     old_line = line = *lp;
16118     ansi = len = clen = 0;
16119
16120     for (i=0; i < count; i++)
16121     {
16122         if (src[i] == '\033')
16123             ansi = 1;
16124
16125         // if we hit the width, back up
16126         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16127         {
16128             // store i & len in case the word is too long
16129             old_i = i, old_len = len;
16130
16131             // find the end of the last word
16132             while (i && src[i] != ' ' && src[i] != '\n')
16133             {
16134                 i--;
16135                 len--;
16136             }
16137
16138             // word too long?  restore i & len before splitting it
16139             if ((old_i-i+clen) >= width)
16140             {
16141                 i = old_i;
16142                 len = old_len;
16143             }
16144
16145             // extra space?
16146             if (i && src[i-1] == ' ')
16147                 len--;
16148
16149             if (src[i] != ' ' && src[i] != '\n')
16150             {
16151                 i--;
16152                 if (len)
16153                     len--;
16154             }
16155
16156             // now append the newline and continuation sequence
16157             if (dest)
16158                 dest[len] = '\n';
16159             len++;
16160             if (dest)
16161                 strncpy(dest+len, cseq, cseq_len);
16162             len += cseq_len;
16163             line = cseq_len;
16164             clen = cseq_len;
16165             continue;
16166         }
16167
16168         if (dest)
16169             dest[len] = src[i];
16170         len++;
16171         if (!ansi)
16172             line++;
16173         if (src[i] == '\n')
16174             line = 0;
16175         if (src[i] == 'm')
16176             ansi = 0;
16177     }
16178     if (dest && appData.debugMode)
16179     {
16180         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16181             count, width, line, len, *lp);
16182         show_bytes(debugFP, src, count);
16183         fprintf(debugFP, "\ndest: ");
16184         show_bytes(debugFP, dest, len);
16185         fprintf(debugFP, "\n");
16186     }
16187     *lp = dest ? line : old_line;
16188
16189     return len;
16190 }
16191
16192 // [HGM] vari: routines for shelving variations
16193
16194 void
16195 PushInner(int firstMove, int lastMove)
16196 {
16197         int i, j, nrMoves = lastMove - firstMove;
16198
16199         // push current tail of game on stack
16200         savedResult[storedGames] = gameInfo.result;
16201         savedDetails[storedGames] = gameInfo.resultDetails;
16202         gameInfo.resultDetails = NULL;
16203         savedFirst[storedGames] = firstMove;
16204         savedLast [storedGames] = lastMove;
16205         savedFramePtr[storedGames] = framePtr;
16206         framePtr -= nrMoves; // reserve space for the boards
16207         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16208             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16209             for(j=0; j<MOVE_LEN; j++)
16210                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16211             for(j=0; j<2*MOVE_LEN; j++)
16212                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16213             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16214             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16215             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16216             pvInfoList[firstMove+i-1].depth = 0;
16217             commentList[framePtr+i] = commentList[firstMove+i];
16218             commentList[firstMove+i] = NULL;
16219         }
16220
16221         storedGames++;
16222         forwardMostMove = firstMove; // truncate game so we can start variation
16223 }
16224
16225 void
16226 PushTail(int firstMove, int lastMove)
16227 {
16228         if(appData.icsActive) { // only in local mode
16229                 forwardMostMove = currentMove; // mimic old ICS behavior
16230                 return;
16231         }
16232         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16233
16234         PushInner(firstMove, lastMove);
16235         if(storedGames == 1) GreyRevert(FALSE);
16236 }
16237
16238 void
16239 PopInner(Boolean annotate)
16240 {
16241         int i, j, nrMoves;
16242         char buf[8000], moveBuf[20];
16243
16244         storedGames--;
16245         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16246         nrMoves = savedLast[storedGames] - currentMove;
16247         if(annotate) {
16248                 int cnt = 10;
16249                 if(!WhiteOnMove(currentMove))
16250                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16251                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16252                 for(i=currentMove; i<forwardMostMove; i++) {
16253                         if(WhiteOnMove(i))
16254                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16255                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16256                         strcat(buf, moveBuf);
16257                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16258                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16259                 }
16260                 strcat(buf, ")");
16261         }
16262         for(i=1; i<=nrMoves; i++) { // copy last variation back
16263             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16264             for(j=0; j<MOVE_LEN; j++)
16265                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16266             for(j=0; j<2*MOVE_LEN; j++)
16267                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16268             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16269             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16270             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16271             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16272             commentList[currentMove+i] = commentList[framePtr+i];
16273             commentList[framePtr+i] = NULL;
16274         }
16275         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16276         framePtr = savedFramePtr[storedGames];
16277         gameInfo.result = savedResult[storedGames];
16278         if(gameInfo.resultDetails != NULL) {
16279             free(gameInfo.resultDetails);
16280       }
16281         gameInfo.resultDetails = savedDetails[storedGames];
16282         forwardMostMove = currentMove + nrMoves;
16283 }
16284
16285 Boolean
16286 PopTail(Boolean annotate)
16287 {
16288         if(appData.icsActive) return FALSE; // only in local mode
16289         if(!storedGames) return FALSE; // sanity
16290         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16291
16292         PopInner(annotate);
16293
16294         if(storedGames == 0) GreyRevert(TRUE);
16295         return TRUE;
16296 }
16297
16298 void
16299 CleanupTail()
16300 {       // remove all shelved variations
16301         int i;
16302         for(i=0; i<storedGames; i++) {
16303             if(savedDetails[i])
16304                 free(savedDetails[i]);
16305             savedDetails[i] = NULL;
16306         }
16307         for(i=framePtr; i<MAX_MOVES; i++) {
16308                 if(commentList[i]) free(commentList[i]);
16309                 commentList[i] = NULL;
16310         }
16311         framePtr = MAX_MOVES-1;
16312         storedGames = 0;
16313 }
16314
16315 void
16316 LoadVariation(int index, char *text)
16317 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16318         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16319         int level = 0, move;
16320
16321         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16322         // first find outermost bracketing variation
16323         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16324             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16325                 if(*p == '{') wait = '}'; else
16326                 if(*p == '[') wait = ']'; else
16327                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16328                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16329             }
16330             if(*p == wait) wait = NULLCHAR; // closing ]} found
16331             p++;
16332         }
16333         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16334         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16335         end[1] = NULLCHAR; // clip off comment beyond variation
16336         ToNrEvent(currentMove-1);
16337         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16338         // kludge: use ParsePV() to append variation to game
16339         move = currentMove;
16340         ParsePV(start, TRUE, TRUE);
16341         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16342         ClearPremoveHighlights();
16343         CommentPopDown();
16344         ToNrEvent(currentMove+1);
16345 }
16346