4ec6313120146d137c525af24b5da7600d17fc07
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237
238 #ifdef WIN32
239        extern void ConsoleCreate();
240 #endif
241
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
245
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 Boolean abortMatch;
254
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 int endPV = -1;
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
262 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
266 Boolean partnerUp;
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
278 int chattingPartner;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
284
285 /* States for ics_getting_history */
286 #define H_FALSE 0
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
292
293 /* whosays values for GameEnds */
294 #define GE_ICS 0
295 #define GE_ENGINE 1
296 #define GE_PLAYER 2
297 #define GE_FILE 3
298 #define GE_XBOARD 4
299 #define GE_ENGINE1 5
300 #define GE_ENGINE2 6
301
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
304
305 /* Different types of move when calling RegisterMove */
306 #define CMAIL_MOVE   0
307 #define CMAIL_RESIGN 1
308 #define CMAIL_DRAW   2
309 #define CMAIL_ACCEPT 3
310
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
315
316 /* Telnet protocol constants */
317 #define TN_WILL 0373
318 #define TN_WONT 0374
319 #define TN_DO   0375
320 #define TN_DONT 0376
321 #define TN_IAC  0377
322 #define TN_ECHO 0001
323 #define TN_SGA  0003
324 #define TN_PORT 23
325
326 char*
327 safeStrCpy( char *dst, const char *src, size_t count )
328 { // [HGM] made safe
329   int i;
330   assert( dst != NULL );
331   assert( src != NULL );
332   assert( count > 0 );
333
334   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335   if(  i == count && dst[count-1] != NULLCHAR)
336     {
337       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338       if(appData.debugMode)
339       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
340     }
341
342   return dst;
343 }
344
345 /* Some compiler can't cast u64 to double
346  * This function do the job for us:
347
348  * We use the highest bit for cast, this only
349  * works if the highest bit is not
350  * in use (This should not happen)
351  *
352  * We used this for all compiler
353  */
354 double
355 u64ToDouble(u64 value)
356 {
357   double r;
358   u64 tmp = value & u64Const(0x7fffffffffffffff);
359   r = (double)(s64)tmp;
360   if (value & u64Const(0x8000000000000000))
361        r +=  9.2233720368547758080e18; /* 2^63 */
362  return r;
363 }
364
365 /* Fake up flags for now, as we aren't keeping track of castling
366    availability yet. [HGM] Change of logic: the flag now only
367    indicates the type of castlings allowed by the rule of the game.
368    The actual rights themselves are maintained in the array
369    castlingRights, as part of the game history, and are not probed
370    by this function.
371  */
372 int
373 PosFlags(index)
374 {
375   int flags = F_ALL_CASTLE_OK;
376   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377   switch (gameInfo.variant) {
378   case VariantSuicide:
379     flags &= ~F_ALL_CASTLE_OK;
380   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381     flags |= F_IGNORE_CHECK;
382   case VariantLosers:
383     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384     break;
385   case VariantAtomic:
386     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387     break;
388   case VariantKriegspiel:
389     flags |= F_KRIEGSPIEL_CAPTURE;
390     break;
391   case VariantCapaRandom:
392   case VariantFischeRandom:
393     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394   case VariantNoCastle:
395   case VariantShatranj:
396   case VariantCourier:
397   case VariantMakruk:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 int shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating(str)
650   char *str;
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine(ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions(ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740 "first",
741 "second"
742 };
743
744 void
745 InitEngine(ChessProgramState *cps, int n)
746 {   // [HGM] all engine initialiation put in a function that does one engine
747
748     ClearOptions(cps);
749
750     cps->which = engineNames[n];
751     cps->maybeThinking = FALSE;
752     cps->pr = NoProc;
753     cps->isr = NULL;
754     cps->sendTime = 2;
755     cps->sendDrawOffers = 1;
756
757     cps->program = appData.chessProgram[n];
758     cps->host = appData.host[n];
759     cps->dir = appData.directory[n];
760     cps->initString = appData.engInitString[n];
761     cps->computerString = appData.computerString[n];
762     cps->useSigint  = TRUE;
763     cps->useSigterm = TRUE;
764     cps->reuse = appData.reuse[n];
765     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
766     cps->useSetboard = FALSE;
767     cps->useSAN = FALSE;
768     cps->usePing = FALSE;
769     cps->lastPing = 0;
770     cps->lastPong = 0;
771     cps->usePlayother = FALSE;
772     cps->useColors = TRUE;
773     cps->useUsermove = FALSE;
774     cps->sendICS = FALSE;
775     cps->sendName = appData.icsActive;
776     cps->sdKludge = FALSE;
777     cps->stKludge = FALSE;
778     TidyProgramName(cps->program, cps->host, cps->tidy);
779     cps->matchWins = 0;
780     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781     cps->analysisSupport = 2; /* detect */
782     cps->analyzing = FALSE;
783     cps->initDone = FALSE;
784
785     /* New features added by Tord: */
786     cps->useFEN960 = FALSE;
787     cps->useOOCastle = TRUE;
788     /* End of new features added by Tord. */
789     cps->fenOverride  = appData.fenOverride[n];
790
791     /* [HGM] time odds: set factor for each machine */
792     cps->timeOdds  = appData.timeOdds[n];
793
794     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795     cps->accumulateTC = appData.accumulateTC[n];
796     cps->maxNrOfSessions = 1;
797
798     /* [HGM] debug */
799     cps->debug = FALSE;
800
801     cps->supportsNPS = UNKNOWN;
802     cps->memSize = FALSE;
803     cps->maxCores = FALSE;
804     cps->egtFormats[0] = NULLCHAR;
805
806     /* [HGM] options */
807     cps->optionSettings  = appData.engOptions[n];
808
809     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810     cps->isUCI = appData.isUCI[n]; /* [AS] */
811     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
812
813     if (appData.protocolVersion[n] > PROTOVER
814         || appData.protocolVersion[n] < 1)
815       {
816         char buf[MSG_SIZ];
817         int len;
818
819         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820                        appData.protocolVersion[n]);
821         if( (len > MSG_SIZ) && appData.debugMode )
822           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
823
824         DisplayFatalError(buf, 0, 2);
825       }
826     else
827       {
828         cps->protocolVersion = appData.protocolVersion[n];
829       }
830
831     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
832 }
833
834 ChessProgramState *savCps;
835
836 void
837 LoadEngine()
838 {
839     int i;
840     if(WaitForEngine(savCps, LoadEngine)) return;
841     CommonEngineInit(); // recalculate time odds
842     if(gameInfo.variant != StringToVariant(appData.variant)) {
843         // we changed variant when loading the engine; this forces us to reset
844         Reset(TRUE, savCps != &first);
845         EditGameEvent(); // for consistency with other path, as Reset changes mode
846     }
847     InitChessProgram(savCps, FALSE);
848     SendToProgram("force\n", savCps);
849     DisplayMessage("", "");
850     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852     ThawUI();
853     SetGNUMode();
854 }
855
856 void
857 ReplaceEngine(ChessProgramState *cps, int n)
858 {
859     EditGameEvent();
860     UnloadEngine(cps);
861     appData.noChessProgram = FALSE;
862     appData.clockMode = TRUE;
863     InitEngine(cps, n);
864     UpdateLogos(TRUE);
865     if(n) return; // only startup first engine immediately; second can wait
866     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
867     LoadEngine();
868 }
869
870 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
871 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
872
873 static char resetOptions[] = 
874         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
875         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
876
877 void
878 Load(ChessProgramState *cps, int i)
879 {
880     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
881     if(engineLine[0]) { // an engine was selected from the combo box
882         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
883         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
884         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
885         ParseArgsFromString(buf);
886         SwapEngines(i);
887         ReplaceEngine(cps, i);
888         return;
889     }
890     p = engineName;
891     while(q = strchr(p, SLASH)) p = q+1;
892     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
893     if(engineDir[0] != NULLCHAR)
894         appData.directory[i] = engineDir;
895     else if(p != engineName) { // derive directory from engine path, when not given
896         p[-1] = 0;
897         appData.directory[i] = strdup(engineName);
898         p[-1] = SLASH;
899     } else appData.directory[i] = ".";
900     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901     if(params[0]) {
902         snprintf(command, MSG_SIZ, "%s %s", p, params);
903         p = command;
904     }
905     appData.chessProgram[i] = strdup(p);
906     appData.isUCI[i] = isUCI;
907     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
908     appData.hasOwnBookUCI[i] = hasBook;
909     if(!nickName[0]) useNick = FALSE;
910     if(useNick) ASSIGN(appData.pgnName[i], nickName);
911     if(addToList) {
912         int len;
913         char quote;
914         q = firstChessProgramNames;
915         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
916         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
917         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
918                         quote, p, quote, appData.directory[i], 
919                         useNick ? " -fn \"" : "",
920                         useNick ? nickName : "",
921                         useNick ? "\"" : "",
922                         v1 ? " -firstProtocolVersion 1" : "",
923                         hasBook ? "" : " -fNoOwnBookUCI",
924                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
925                         storeVariant ? " -variant " : "",
926                         storeVariant ? VariantName(gameInfo.variant) : "");
927         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
928         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
929         if(q)   free(q);
930     }
931     ReplaceEngine(cps, i);
932 }
933
934 void
935 InitTimeControls()
936 {
937     int matched, min, sec;
938     /*
939      * Parse timeControl resource
940      */
941     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
942                           appData.movesPerSession)) {
943         char buf[MSG_SIZ];
944         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
945         DisplayFatalError(buf, 0, 2);
946     }
947
948     /*
949      * Parse searchTime resource
950      */
951     if (*appData.searchTime != NULLCHAR) {
952         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
953         if (matched == 1) {
954             searchTime = min * 60;
955         } else if (matched == 2) {
956             searchTime = min * 60 + sec;
957         } else {
958             char buf[MSG_SIZ];
959             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
960             DisplayFatalError(buf, 0, 2);
961         }
962     }
963 }
964
965 void
966 InitBackEnd1()
967 {
968
969     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
970     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
971
972     GetTimeMark(&programStartTime);
973     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len > MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len > MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for daw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantSpartan:    /* should work */
1122         break;
1123       }
1124     }
1125
1126 }
1127
1128 int NextIntegerFromString( char ** str, long * value )
1129 {
1130     int result = -1;
1131     char * s = *str;
1132
1133     while( *s == ' ' || *s == '\t' ) {
1134         s++;
1135     }
1136
1137     *value = 0;
1138
1139     if( *s >= '0' && *s <= '9' ) {
1140         while( *s >= '0' && *s <= '9' ) {
1141             *value = *value * 10 + (*s - '0');
1142             s++;
1143         }
1144
1145         result = 0;
1146     }
1147
1148     *str = s;
1149
1150     return result;
1151 }
1152
1153 int NextTimeControlFromString( char ** str, long * value )
1154 {
1155     long temp;
1156     int result = NextIntegerFromString( str, &temp );
1157
1158     if( result == 0 ) {
1159         *value = temp * 60; /* Minutes */
1160         if( **str == ':' ) {
1161             (*str)++;
1162             result = NextIntegerFromString( str, &temp );
1163             *value += temp; /* Seconds */
1164         }
1165     }
1166
1167     return result;
1168 }
1169
1170 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1171 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1172     int result = -1, type = 0; long temp, temp2;
1173
1174     if(**str != ':') return -1; // old params remain in force!
1175     (*str)++;
1176     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1177     if( NextIntegerFromString( str, &temp ) ) return -1;
1178     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1179
1180     if(**str != '/') {
1181         /* time only: incremental or sudden-death time control */
1182         if(**str == '+') { /* increment follows; read it */
1183             (*str)++;
1184             if(**str == '!') type = *(*str)++; // Bronstein TC
1185             if(result = NextIntegerFromString( str, &temp2)) return -1;
1186             *inc = temp2 * 1000;
1187             if(**str == '.') { // read fraction of increment
1188                 char *start = ++(*str);
1189                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1190                 temp2 *= 1000;
1191                 while(start++ < *str) temp2 /= 10;
1192                 *inc += temp2;
1193             }
1194         } else *inc = 0;
1195         *moves = 0; *tc = temp * 1000; *incType = type;
1196         return 0;
1197     }
1198
1199     (*str)++; /* classical time control */
1200     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1201
1202     if(result == 0) {
1203         *moves = temp;
1204         *tc    = temp2 * 1000;
1205         *inc   = 0;
1206         *incType = type;
1207     }
1208     return result;
1209 }
1210
1211 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1212 {   /* [HGM] get time to add from the multi-session time-control string */
1213     int incType, moves=1; /* kludge to force reading of first session */
1214     long time, increment;
1215     char *s = tcString;
1216
1217     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1218     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1219     do {
1220         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1221         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1222         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1223         if(movenr == -1) return time;    /* last move before new session     */
1224         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1225         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1226         if(!moves) return increment;     /* current session is incremental   */
1227         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1228     } while(movenr >= -1);               /* try again for next session       */
1229
1230     return 0; // no new time quota on this move
1231 }
1232
1233 int
1234 ParseTimeControl(tc, ti, mps)
1235      char *tc;
1236      float ti;
1237      int mps;
1238 {
1239   long tc1;
1240   long tc2;
1241   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1242   int min, sec=0;
1243
1244   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1245   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1246       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1247   if(ti > 0) {
1248
1249     if(mps)
1250       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1251     else 
1252       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1253   } else {
1254     if(mps)
1255       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1256     else 
1257       snprintf(buf, MSG_SIZ, ":%s", mytc);
1258   }
1259   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1260   
1261   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1262     return FALSE;
1263   }
1264
1265   if( *tc == '/' ) {
1266     /* Parse second time control */
1267     tc++;
1268
1269     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1270       return FALSE;
1271     }
1272
1273     if( tc2 == 0 ) {
1274       return FALSE;
1275     }
1276
1277     timeControl_2 = tc2 * 1000;
1278   }
1279   else {
1280     timeControl_2 = 0;
1281   }
1282
1283   if( tc1 == 0 ) {
1284     return FALSE;
1285   }
1286
1287   timeControl = tc1 * 1000;
1288
1289   if (ti >= 0) {
1290     timeIncrement = ti * 1000;  /* convert to ms */
1291     movesPerSession = 0;
1292   } else {
1293     timeIncrement = 0;
1294     movesPerSession = mps;
1295   }
1296   return TRUE;
1297 }
1298
1299 void
1300 InitBackEnd2()
1301 {
1302     if (appData.debugMode) {
1303         fprintf(debugFP, "%s\n", programVersion);
1304     }
1305
1306     set_cont_sequence(appData.wrapContSeq);
1307     if (appData.matchGames > 0) {
1308         appData.matchMode = TRUE;
1309     } else if (appData.matchMode) {
1310         appData.matchGames = 1;
1311     }
1312     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1313         appData.matchGames = appData.sameColorGames;
1314     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1315         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1316         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1317     }
1318     Reset(TRUE, FALSE);
1319     if (appData.noChessProgram || first.protocolVersion == 1) {
1320       InitBackEnd3();
1321     } else {
1322       /* kludge: allow timeout for initial "feature" commands */
1323       FreezeUI();
1324       DisplayMessage("", _("Starting chess program"));
1325       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1326     }
1327 }
1328
1329 int
1330 CalculateIndex(int index, int gameNr)
1331 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1332     int res;
1333     if(index > 0) return index; // fixed nmber
1334     if(index == 0) return 1;
1335     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1336     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1337     return res;
1338 }
1339
1340 int
1341 LoadGameOrPosition(int gameNr)
1342 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1343     if (*appData.loadGameFile != NULLCHAR) {
1344         if (!LoadGameFromFile(appData.loadGameFile,
1345                 CalculateIndex(appData.loadGameIndex, gameNr),
1346                               appData.loadGameFile, FALSE)) {
1347             DisplayFatalError(_("Bad game file"), 0, 1);
1348             return 0;
1349         }
1350     } else if (*appData.loadPositionFile != NULLCHAR) {
1351         if (!LoadPositionFromFile(appData.loadPositionFile,
1352                 CalculateIndex(appData.loadPositionIndex, gameNr),
1353                                   appData.loadPositionFile)) {
1354             DisplayFatalError(_("Bad position file"), 0, 1);
1355             return 0;
1356         }
1357     }
1358     return 1;
1359 }
1360
1361 void
1362 ReserveGame(int gameNr, char resChar)
1363 {
1364     FILE *tf = fopen(appData.tourneyFile, "r+");
1365     char *p, *q, c, buf[MSG_SIZ];
1366     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1367     safeStrCpy(buf, lastMsg, MSG_SIZ);
1368     DisplayMessage(_("Pick new game"), "");
1369     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1370     ParseArgsFromFile(tf);
1371     p = q = appData.results;
1372     if(appData.debugMode) {
1373       char *r = appData.participants;
1374       fprintf(debugFP, "results = '%s'\n", p);
1375       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1376       fprintf(debugFP, "\n");
1377     }
1378     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1379     nextGame = q - p;
1380     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1381     safeStrCpy(q, p, strlen(p) + 2);
1382     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1383     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1384     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1385         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1386         q[nextGame] = '*';
1387     }
1388     fseek(tf, -(strlen(p)+4), SEEK_END);
1389     c = fgetc(tf);
1390     if(c != '"') // depending on DOS or Unix line endings we can be one off
1391          fseek(tf, -(strlen(p)+2), SEEK_END);
1392     else fseek(tf, -(strlen(p)+3), SEEK_END);
1393     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1394     DisplayMessage(buf, "");
1395     free(p); appData.results = q;
1396     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1397        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1398         UnloadEngine(&first);  // next game belongs to other pairing;
1399         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1400     }
1401 }
1402
1403 void
1404 MatchEvent(int mode)
1405 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1406         int dummy;
1407         if(matchMode) { // already in match mode: switch it off
1408             abortMatch = TRUE;
1409             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1410             return;
1411         }
1412 //      if(gameMode != BeginningOfGame) {
1413 //          DisplayError(_("You can only start a match from the initial position."), 0);
1414 //          return;
1415 //      }
1416         abortMatch = FALSE;
1417         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1418         /* Set up machine vs. machine match */
1419         nextGame = 0;
1420         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1421         if(appData.tourneyFile[0]) {
1422             ReserveGame(-1, 0);
1423             if(nextGame > appData.matchGames) {
1424                 char buf[MSG_SIZ];
1425                 if(strchr(appData.results, '*') == NULL) {
1426                     FILE *f;
1427                     appData.tourneyCycles++;
1428                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1429                         fclose(f);
1430                         NextTourneyGame(-1, &dummy);
1431                         ReserveGame(-1, 0);
1432                         if(nextGame <= appData.matchGames) {
1433                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1434                             matchMode = mode;
1435                             ScheduleDelayedEvent(NextMatchGame, 10000);
1436                             return;
1437                         }
1438                     }
1439                 }
1440                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1441                 DisplayError(buf, 0);
1442                 appData.tourneyFile[0] = 0;
1443                 return;
1444             }
1445         } else
1446         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1447             DisplayFatalError(_("Can't have a match with no chess programs"),
1448                               0, 2);
1449             return;
1450         }
1451         matchMode = mode;
1452         matchGame = roundNr = 1;
1453         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1454         NextMatchGame();
1455 }
1456
1457 void
1458 InitBackEnd3 P((void))
1459 {
1460     GameMode initialMode;
1461     char buf[MSG_SIZ];
1462     int err, len;
1463
1464     InitChessProgram(&first, startedFromSetupPosition);
1465
1466     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1467         free(programVersion);
1468         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1469         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1470     }
1471
1472     if (appData.icsActive) {
1473 #ifdef WIN32
1474         /* [DM] Make a console window if needed [HGM] merged ifs */
1475         ConsoleCreate();
1476 #endif
1477         err = establish();
1478         if (err != 0)
1479           {
1480             if (*appData.icsCommPort != NULLCHAR)
1481               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1482                              appData.icsCommPort);
1483             else
1484               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1485                         appData.icsHost, appData.icsPort);
1486
1487             if( (len > MSG_SIZ) && appData.debugMode )
1488               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1489
1490             DisplayFatalError(buf, err, 1);
1491             return;
1492         }
1493         SetICSMode();
1494         telnetISR =
1495           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1496         fromUserISR =
1497           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1498         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1499             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1500     } else if (appData.noChessProgram) {
1501         SetNCPMode();
1502     } else {
1503         SetGNUMode();
1504     }
1505
1506     if (*appData.cmailGameName != NULLCHAR) {
1507         SetCmailMode();
1508         OpenLoopback(&cmailPR);
1509         cmailISR =
1510           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1511     }
1512
1513     ThawUI();
1514     DisplayMessage("", "");
1515     if (StrCaseCmp(appData.initialMode, "") == 0) {
1516       initialMode = BeginningOfGame;
1517       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1518         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1519         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1520         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1521         ModeHighlight();
1522       }
1523     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1524       initialMode = TwoMachinesPlay;
1525     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1526       initialMode = AnalyzeFile;
1527     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1528       initialMode = AnalyzeMode;
1529     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1530       initialMode = MachinePlaysWhite;
1531     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1532       initialMode = MachinePlaysBlack;
1533     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1534       initialMode = EditGame;
1535     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1536       initialMode = EditPosition;
1537     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1538       initialMode = Training;
1539     } else {
1540       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1541       if( (len > MSG_SIZ) && appData.debugMode )
1542         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1543
1544       DisplayFatalError(buf, 0, 2);
1545       return;
1546     }
1547
1548     if (appData.matchMode) {
1549         if(appData.tourneyFile[0]) { // start tourney from command line
1550             FILE *f;
1551             if(f = fopen(appData.tourneyFile, "r")) {
1552                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1553                 fclose(f);
1554                 appData.clockMode = TRUE;
1555                 SetGNUMode();
1556             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1557         }
1558         MatchEvent(TRUE);
1559     } else if (*appData.cmailGameName != NULLCHAR) {
1560         /* Set up cmail mode */
1561         ReloadCmailMsgEvent(TRUE);
1562     } else {
1563         /* Set up other modes */
1564         if (initialMode == AnalyzeFile) {
1565           if (*appData.loadGameFile == NULLCHAR) {
1566             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1567             return;
1568           }
1569         }
1570         if (*appData.loadGameFile != NULLCHAR) {
1571             (void) LoadGameFromFile(appData.loadGameFile,
1572                                     appData.loadGameIndex,
1573                                     appData.loadGameFile, TRUE);
1574         } else if (*appData.loadPositionFile != NULLCHAR) {
1575             (void) LoadPositionFromFile(appData.loadPositionFile,
1576                                         appData.loadPositionIndex,
1577                                         appData.loadPositionFile);
1578             /* [HGM] try to make self-starting even after FEN load */
1579             /* to allow automatic setup of fairy variants with wtm */
1580             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1581                 gameMode = BeginningOfGame;
1582                 setboardSpoiledMachineBlack = 1;
1583             }
1584             /* [HGM] loadPos: make that every new game uses the setup */
1585             /* from file as long as we do not switch variant          */
1586             if(!blackPlaysFirst) {
1587                 startedFromPositionFile = TRUE;
1588                 CopyBoard(filePosition, boards[0]);
1589             }
1590         }
1591         if (initialMode == AnalyzeMode) {
1592           if (appData.noChessProgram) {
1593             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1594             return;
1595           }
1596           if (appData.icsActive) {
1597             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1598             return;
1599           }
1600           AnalyzeModeEvent();
1601         } else if (initialMode == AnalyzeFile) {
1602           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1603           ShowThinkingEvent();
1604           AnalyzeFileEvent();
1605           AnalysisPeriodicEvent(1);
1606         } else if (initialMode == MachinePlaysWhite) {
1607           if (appData.noChessProgram) {
1608             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1609                               0, 2);
1610             return;
1611           }
1612           if (appData.icsActive) {
1613             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1614                               0, 2);
1615             return;
1616           }
1617           MachineWhiteEvent();
1618         } else if (initialMode == MachinePlaysBlack) {
1619           if (appData.noChessProgram) {
1620             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1621                               0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1626                               0, 2);
1627             return;
1628           }
1629           MachineBlackEvent();
1630         } else if (initialMode == TwoMachinesPlay) {
1631           if (appData.noChessProgram) {
1632             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1633                               0, 2);
1634             return;
1635           }
1636           if (appData.icsActive) {
1637             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1638                               0, 2);
1639             return;
1640           }
1641           TwoMachinesEvent();
1642         } else if (initialMode == EditGame) {
1643           EditGameEvent();
1644         } else if (initialMode == EditPosition) {
1645           EditPositionEvent();
1646         } else if (initialMode == Training) {
1647           if (*appData.loadGameFile == NULLCHAR) {
1648             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1649             return;
1650           }
1651           TrainingEvent();
1652         }
1653     }
1654 }
1655
1656 /*
1657  * Establish will establish a contact to a remote host.port.
1658  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1659  *  used to talk to the host.
1660  * Returns 0 if okay, error code if not.
1661  */
1662 int
1663 establish()
1664 {
1665     char buf[MSG_SIZ];
1666
1667     if (*appData.icsCommPort != NULLCHAR) {
1668         /* Talk to the host through a serial comm port */
1669         return OpenCommPort(appData.icsCommPort, &icsPR);
1670
1671     } else if (*appData.gateway != NULLCHAR) {
1672         if (*appData.remoteShell == NULLCHAR) {
1673             /* Use the rcmd protocol to run telnet program on a gateway host */
1674             snprintf(buf, sizeof(buf), "%s %s %s",
1675                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1676             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1677
1678         } else {
1679             /* Use the rsh program to run telnet program on a gateway host */
1680             if (*appData.remoteUser == NULLCHAR) {
1681                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1682                         appData.gateway, appData.telnetProgram,
1683                         appData.icsHost, appData.icsPort);
1684             } else {
1685                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1686                         appData.remoteShell, appData.gateway,
1687                         appData.remoteUser, appData.telnetProgram,
1688                         appData.icsHost, appData.icsPort);
1689             }
1690             return StartChildProcess(buf, "", &icsPR);
1691
1692         }
1693     } else if (appData.useTelnet) {
1694         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1695
1696     } else {
1697         /* TCP socket interface differs somewhat between
1698            Unix and NT; handle details in the front end.
1699            */
1700         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1701     }
1702 }
1703
1704 void EscapeExpand(char *p, char *q)
1705 {       // [HGM] initstring: routine to shape up string arguments
1706         while(*p++ = *q++) if(p[-1] == '\\')
1707             switch(*q++) {
1708                 case 'n': p[-1] = '\n'; break;
1709                 case 'r': p[-1] = '\r'; break;
1710                 case 't': p[-1] = '\t'; break;
1711                 case '\\': p[-1] = '\\'; break;
1712                 case 0: *p = 0; return;
1713                 default: p[-1] = q[-1]; break;
1714             }
1715 }
1716
1717 void
1718 show_bytes(fp, buf, count)
1719      FILE *fp;
1720      char *buf;
1721      int count;
1722 {
1723     while (count--) {
1724         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1725             fprintf(fp, "\\%03o", *buf & 0xff);
1726         } else {
1727             putc(*buf, fp);
1728         }
1729         buf++;
1730     }
1731     fflush(fp);
1732 }
1733
1734 /* Returns an errno value */
1735 int
1736 OutputMaybeTelnet(pr, message, count, outError)
1737      ProcRef pr;
1738      char *message;
1739      int count;
1740      int *outError;
1741 {
1742     char buf[8192], *p, *q, *buflim;
1743     int left, newcount, outcount;
1744
1745     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1746         *appData.gateway != NULLCHAR) {
1747         if (appData.debugMode) {
1748             fprintf(debugFP, ">ICS: ");
1749             show_bytes(debugFP, message, count);
1750             fprintf(debugFP, "\n");
1751         }
1752         return OutputToProcess(pr, message, count, outError);
1753     }
1754
1755     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1756     p = message;
1757     q = buf;
1758     left = count;
1759     newcount = 0;
1760     while (left) {
1761         if (q >= buflim) {
1762             if (appData.debugMode) {
1763                 fprintf(debugFP, ">ICS: ");
1764                 show_bytes(debugFP, buf, newcount);
1765                 fprintf(debugFP, "\n");
1766             }
1767             outcount = OutputToProcess(pr, buf, newcount, outError);
1768             if (outcount < newcount) return -1; /* to be sure */
1769             q = buf;
1770             newcount = 0;
1771         }
1772         if (*p == '\n') {
1773             *q++ = '\r';
1774             newcount++;
1775         } else if (((unsigned char) *p) == TN_IAC) {
1776             *q++ = (char) TN_IAC;
1777             newcount ++;
1778         }
1779         *q++ = *p++;
1780         newcount++;
1781         left--;
1782     }
1783     if (appData.debugMode) {
1784         fprintf(debugFP, ">ICS: ");
1785         show_bytes(debugFP, buf, newcount);
1786         fprintf(debugFP, "\n");
1787     }
1788     outcount = OutputToProcess(pr, buf, newcount, outError);
1789     if (outcount < newcount) return -1; /* to be sure */
1790     return count;
1791 }
1792
1793 void
1794 read_from_player(isr, closure, message, count, error)
1795      InputSourceRef isr;
1796      VOIDSTAR closure;
1797      char *message;
1798      int count;
1799      int error;
1800 {
1801     int outError, outCount;
1802     static int gotEof = 0;
1803
1804     /* Pass data read from player on to ICS */
1805     if (count > 0) {
1806         gotEof = 0;
1807         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1808         if (outCount < count) {
1809             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1810         }
1811     } else if (count < 0) {
1812         RemoveInputSource(isr);
1813         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1814     } else if (gotEof++ > 0) {
1815         RemoveInputSource(isr);
1816         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1817     }
1818 }
1819
1820 void
1821 KeepAlive()
1822 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1823     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1824     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1825     SendToICS("date\n");
1826     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1827 }
1828
1829 /* added routine for printf style output to ics */
1830 void ics_printf(char *format, ...)
1831 {
1832     char buffer[MSG_SIZ];
1833     va_list args;
1834
1835     va_start(args, format);
1836     vsnprintf(buffer, sizeof(buffer), format, args);
1837     buffer[sizeof(buffer)-1] = '\0';
1838     SendToICS(buffer);
1839     va_end(args);
1840 }
1841
1842 void
1843 SendToICS(s)
1844      char *s;
1845 {
1846     int count, outCount, outError;
1847
1848     if (icsPR == NULL) return;
1849
1850     count = strlen(s);
1851     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1852     if (outCount < count) {
1853         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854     }
1855 }
1856
1857 /* This is used for sending logon scripts to the ICS. Sending
1858    without a delay causes problems when using timestamp on ICC
1859    (at least on my machine). */
1860 void
1861 SendToICSDelayed(s,msdelay)
1862      char *s;
1863      long msdelay;
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NULL) return;
1868
1869     count = strlen(s);
1870     if (appData.debugMode) {
1871         fprintf(debugFP, ">ICS: ");
1872         show_bytes(debugFP, s, count);
1873         fprintf(debugFP, "\n");
1874     }
1875     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1876                                       msdelay);
1877     if (outCount < count) {
1878         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879     }
1880 }
1881
1882
1883 /* Remove all highlighting escape sequences in s
1884    Also deletes any suffix starting with '('
1885    */
1886 char *
1887 StripHighlightAndTitle(s)
1888      char *s;
1889 {
1890     static char retbuf[MSG_SIZ];
1891     char *p = retbuf;
1892
1893     while (*s != NULLCHAR) {
1894         while (*s == '\033') {
1895             while (*s != NULLCHAR && !isalpha(*s)) s++;
1896             if (*s != NULLCHAR) s++;
1897         }
1898         while (*s != NULLCHAR && *s != '\033') {
1899             if (*s == '(' || *s == '[') {
1900                 *p = NULLCHAR;
1901                 return retbuf;
1902             }
1903             *p++ = *s++;
1904         }
1905     }
1906     *p = NULLCHAR;
1907     return retbuf;
1908 }
1909
1910 /* Remove all highlighting escape sequences in s */
1911 char *
1912 StripHighlight(s)
1913      char *s;
1914 {
1915     static char retbuf[MSG_SIZ];
1916     char *p = retbuf;
1917
1918     while (*s != NULLCHAR) {
1919         while (*s == '\033') {
1920             while (*s != NULLCHAR && !isalpha(*s)) s++;
1921             if (*s != NULLCHAR) s++;
1922         }
1923         while (*s != NULLCHAR && *s != '\033') {
1924             *p++ = *s++;
1925         }
1926     }
1927     *p = NULLCHAR;
1928     return retbuf;
1929 }
1930
1931 char *variantNames[] = VARIANT_NAMES;
1932 char *
1933 VariantName(v)
1934      VariantClass v;
1935 {
1936     return variantNames[v];
1937 }
1938
1939
1940 /* Identify a variant from the strings the chess servers use or the
1941    PGN Variant tag names we use. */
1942 VariantClass
1943 StringToVariant(e)
1944      char *e;
1945 {
1946     char *p;
1947     int wnum = -1;
1948     VariantClass v = VariantNormal;
1949     int i, found = FALSE;
1950     char buf[MSG_SIZ];
1951     int len;
1952
1953     if (!e) return v;
1954
1955     /* [HGM] skip over optional board-size prefixes */
1956     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1957         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1958         while( *e++ != '_');
1959     }
1960
1961     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1962         v = VariantNormal;
1963         found = TRUE;
1964     } else
1965     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1966       if (StrCaseStr(e, variantNames[i])) {
1967         v = (VariantClass) i;
1968         found = TRUE;
1969         break;
1970       }
1971     }
1972
1973     if (!found) {
1974       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1975           || StrCaseStr(e, "wild/fr")
1976           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1977         v = VariantFischeRandom;
1978       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1979                  (i = 1, p = StrCaseStr(e, "w"))) {
1980         p += i;
1981         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1982         if (isdigit(*p)) {
1983           wnum = atoi(p);
1984         } else {
1985           wnum = -1;
1986         }
1987         switch (wnum) {
1988         case 0: /* FICS only, actually */
1989         case 1:
1990           /* Castling legal even if K starts on d-file */
1991           v = VariantWildCastle;
1992           break;
1993         case 2:
1994         case 3:
1995         case 4:
1996           /* Castling illegal even if K & R happen to start in
1997              normal positions. */
1998           v = VariantNoCastle;
1999           break;
2000         case 5:
2001         case 7:
2002         case 8:
2003         case 10:
2004         case 11:
2005         case 12:
2006         case 13:
2007         case 14:
2008         case 15:
2009         case 18:
2010         case 19:
2011           /* Castling legal iff K & R start in normal positions */
2012           v = VariantNormal;
2013           break;
2014         case 6:
2015         case 20:
2016         case 21:
2017           /* Special wilds for position setup; unclear what to do here */
2018           v = VariantLoadable;
2019           break;
2020         case 9:
2021           /* Bizarre ICC game */
2022           v = VariantTwoKings;
2023           break;
2024         case 16:
2025           v = VariantKriegspiel;
2026           break;
2027         case 17:
2028           v = VariantLosers;
2029           break;
2030         case 22:
2031           v = VariantFischeRandom;
2032           break;
2033         case 23:
2034           v = VariantCrazyhouse;
2035           break;
2036         case 24:
2037           v = VariantBughouse;
2038           break;
2039         case 25:
2040           v = Variant3Check;
2041           break;
2042         case 26:
2043           /* Not quite the same as FICS suicide! */
2044           v = VariantGiveaway;
2045           break;
2046         case 27:
2047           v = VariantAtomic;
2048           break;
2049         case 28:
2050           v = VariantShatranj;
2051           break;
2052
2053         /* Temporary names for future ICC types.  The name *will* change in
2054            the next xboard/WinBoard release after ICC defines it. */
2055         case 29:
2056           v = Variant29;
2057           break;
2058         case 30:
2059           v = Variant30;
2060           break;
2061         case 31:
2062           v = Variant31;
2063           break;
2064         case 32:
2065           v = Variant32;
2066           break;
2067         case 33:
2068           v = Variant33;
2069           break;
2070         case 34:
2071           v = Variant34;
2072           break;
2073         case 35:
2074           v = Variant35;
2075           break;
2076         case 36:
2077           v = Variant36;
2078           break;
2079         case 37:
2080           v = VariantShogi;
2081           break;
2082         case 38:
2083           v = VariantXiangqi;
2084           break;
2085         case 39:
2086           v = VariantCourier;
2087           break;
2088         case 40:
2089           v = VariantGothic;
2090           break;
2091         case 41:
2092           v = VariantCapablanca;
2093           break;
2094         case 42:
2095           v = VariantKnightmate;
2096           break;
2097         case 43:
2098           v = VariantFairy;
2099           break;
2100         case 44:
2101           v = VariantCylinder;
2102           break;
2103         case 45:
2104           v = VariantFalcon;
2105           break;
2106         case 46:
2107           v = VariantCapaRandom;
2108           break;
2109         case 47:
2110           v = VariantBerolina;
2111           break;
2112         case 48:
2113           v = VariantJanus;
2114           break;
2115         case 49:
2116           v = VariantSuper;
2117           break;
2118         case 50:
2119           v = VariantGreat;
2120           break;
2121         case -1:
2122           /* Found "wild" or "w" in the string but no number;
2123              must assume it's normal chess. */
2124           v = VariantNormal;
2125           break;
2126         default:
2127           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2128           if( (len > MSG_SIZ) && appData.debugMode )
2129             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2130
2131           DisplayError(buf, 0);
2132           v = VariantUnknown;
2133           break;
2134         }
2135       }
2136     }
2137     if (appData.debugMode) {
2138       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2139               e, wnum, VariantName(v));
2140     }
2141     return v;
2142 }
2143
2144 static int leftover_start = 0, leftover_len = 0;
2145 char star_match[STAR_MATCH_N][MSG_SIZ];
2146
2147 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2148    advance *index beyond it, and set leftover_start to the new value of
2149    *index; else return FALSE.  If pattern contains the character '*', it
2150    matches any sequence of characters not containing '\r', '\n', or the
2151    character following the '*' (if any), and the matched sequence(s) are
2152    copied into star_match.
2153    */
2154 int
2155 looking_at(buf, index, pattern)
2156      char *buf;
2157      int *index;
2158      char *pattern;
2159 {
2160     char *bufp = &buf[*index], *patternp = pattern;
2161     int star_count = 0;
2162     char *matchp = star_match[0];
2163
2164     for (;;) {
2165         if (*patternp == NULLCHAR) {
2166             *index = leftover_start = bufp - buf;
2167             *matchp = NULLCHAR;
2168             return TRUE;
2169         }
2170         if (*bufp == NULLCHAR) return FALSE;
2171         if (*patternp == '*') {
2172             if (*bufp == *(patternp + 1)) {
2173                 *matchp = NULLCHAR;
2174                 matchp = star_match[++star_count];
2175                 patternp += 2;
2176                 bufp++;
2177                 continue;
2178             } else if (*bufp == '\n' || *bufp == '\r') {
2179                 patternp++;
2180                 if (*patternp == NULLCHAR)
2181                   continue;
2182                 else
2183                   return FALSE;
2184             } else {
2185                 *matchp++ = *bufp++;
2186                 continue;
2187             }
2188         }
2189         if (*patternp != *bufp) return FALSE;
2190         patternp++;
2191         bufp++;
2192     }
2193 }
2194
2195 void
2196 SendToPlayer(data, length)
2197      char *data;
2198      int length;
2199 {
2200     int error, outCount;
2201     outCount = OutputToProcess(NoProc, data, length, &error);
2202     if (outCount < length) {
2203         DisplayFatalError(_("Error writing to display"), error, 1);
2204     }
2205 }
2206
2207 void
2208 PackHolding(packed, holding)
2209      char packed[];
2210      char *holding;
2211 {
2212     char *p = holding;
2213     char *q = packed;
2214     int runlength = 0;
2215     int curr = 9999;
2216     do {
2217         if (*p == curr) {
2218             runlength++;
2219         } else {
2220             switch (runlength) {
2221               case 0:
2222                 break;
2223               case 1:
2224                 *q++ = curr;
2225                 break;
2226               case 2:
2227                 *q++ = curr;
2228                 *q++ = curr;
2229                 break;
2230               default:
2231                 sprintf(q, "%d", runlength);
2232                 while (*q) q++;
2233                 *q++ = curr;
2234                 break;
2235             }
2236             runlength = 1;
2237             curr = *p;
2238         }
2239     } while (*p++);
2240     *q = NULLCHAR;
2241 }
2242
2243 /* Telnet protocol requests from the front end */
2244 void
2245 TelnetRequest(ddww, option)
2246      unsigned char ddww, option;
2247 {
2248     unsigned char msg[3];
2249     int outCount, outError;
2250
2251     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2252
2253     if (appData.debugMode) {
2254         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2255         switch (ddww) {
2256           case TN_DO:
2257             ddwwStr = "DO";
2258             break;
2259           case TN_DONT:
2260             ddwwStr = "DONT";
2261             break;
2262           case TN_WILL:
2263             ddwwStr = "WILL";
2264             break;
2265           case TN_WONT:
2266             ddwwStr = "WONT";
2267             break;
2268           default:
2269             ddwwStr = buf1;
2270             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2271             break;
2272         }
2273         switch (option) {
2274           case TN_ECHO:
2275             optionStr = "ECHO";
2276             break;
2277           default:
2278             optionStr = buf2;
2279             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2280             break;
2281         }
2282         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2283     }
2284     msg[0] = TN_IAC;
2285     msg[1] = ddww;
2286     msg[2] = option;
2287     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2288     if (outCount < 3) {
2289         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2290     }
2291 }
2292
2293 void
2294 DoEcho()
2295 {
2296     if (!appData.icsActive) return;
2297     TelnetRequest(TN_DO, TN_ECHO);
2298 }
2299
2300 void
2301 DontEcho()
2302 {
2303     if (!appData.icsActive) return;
2304     TelnetRequest(TN_DONT, TN_ECHO);
2305 }
2306
2307 void
2308 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2309 {
2310     /* put the holdings sent to us by the server on the board holdings area */
2311     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2312     char p;
2313     ChessSquare piece;
2314
2315     if(gameInfo.holdingsWidth < 2)  return;
2316     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2317         return; // prevent overwriting by pre-board holdings
2318
2319     if( (int)lowestPiece >= BlackPawn ) {
2320         holdingsColumn = 0;
2321         countsColumn = 1;
2322         holdingsStartRow = BOARD_HEIGHT-1;
2323         direction = -1;
2324     } else {
2325         holdingsColumn = BOARD_WIDTH-1;
2326         countsColumn = BOARD_WIDTH-2;
2327         holdingsStartRow = 0;
2328         direction = 1;
2329     }
2330
2331     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2332         board[i][holdingsColumn] = EmptySquare;
2333         board[i][countsColumn]   = (ChessSquare) 0;
2334     }
2335     while( (p=*holdings++) != NULLCHAR ) {
2336         piece = CharToPiece( ToUpper(p) );
2337         if(piece == EmptySquare) continue;
2338         /*j = (int) piece - (int) WhitePawn;*/
2339         j = PieceToNumber(piece);
2340         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2341         if(j < 0) continue;               /* should not happen */
2342         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2343         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2344         board[holdingsStartRow+j*direction][countsColumn]++;
2345     }
2346 }
2347
2348
2349 void
2350 VariantSwitch(Board board, VariantClass newVariant)
2351 {
2352    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2353    static Board oldBoard;
2354
2355    startedFromPositionFile = FALSE;
2356    if(gameInfo.variant == newVariant) return;
2357
2358    /* [HGM] This routine is called each time an assignment is made to
2359     * gameInfo.variant during a game, to make sure the board sizes
2360     * are set to match the new variant. If that means adding or deleting
2361     * holdings, we shift the playing board accordingly
2362     * This kludge is needed because in ICS observe mode, we get boards
2363     * of an ongoing game without knowing the variant, and learn about the
2364     * latter only later. This can be because of the move list we requested,
2365     * in which case the game history is refilled from the beginning anyway,
2366     * but also when receiving holdings of a crazyhouse game. In the latter
2367     * case we want to add those holdings to the already received position.
2368     */
2369
2370
2371    if (appData.debugMode) {
2372      fprintf(debugFP, "Switch board from %s to %s\n",
2373              VariantName(gameInfo.variant), VariantName(newVariant));
2374      setbuf(debugFP, NULL);
2375    }
2376    shuffleOpenings = 0;       /* [HGM] shuffle */
2377    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2378    switch(newVariant)
2379      {
2380      case VariantShogi:
2381        newWidth = 9;  newHeight = 9;
2382        gameInfo.holdingsSize = 7;
2383      case VariantBughouse:
2384      case VariantCrazyhouse:
2385        newHoldingsWidth = 2; break;
2386      case VariantGreat:
2387        newWidth = 10;
2388      case VariantSuper:
2389        newHoldingsWidth = 2;
2390        gameInfo.holdingsSize = 8;
2391        break;
2392      case VariantGothic:
2393      case VariantCapablanca:
2394      case VariantCapaRandom:
2395        newWidth = 10;
2396      default:
2397        newHoldingsWidth = gameInfo.holdingsSize = 0;
2398      };
2399
2400    if(newWidth  != gameInfo.boardWidth  ||
2401       newHeight != gameInfo.boardHeight ||
2402       newHoldingsWidth != gameInfo.holdingsWidth ) {
2403
2404      /* shift position to new playing area, if needed */
2405      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2406        for(i=0; i<BOARD_HEIGHT; i++)
2407          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2408            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2409              board[i][j];
2410        for(i=0; i<newHeight; i++) {
2411          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2412          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2413        }
2414      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2415        for(i=0; i<BOARD_HEIGHT; i++)
2416          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2417            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2418              board[i][j];
2419      }
2420      gameInfo.boardWidth  = newWidth;
2421      gameInfo.boardHeight = newHeight;
2422      gameInfo.holdingsWidth = newHoldingsWidth;
2423      gameInfo.variant = newVariant;
2424      InitDrawingSizes(-2, 0);
2425    } else gameInfo.variant = newVariant;
2426    CopyBoard(oldBoard, board);   // remember correctly formatted board
2427      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2428    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2429 }
2430
2431 static int loggedOn = FALSE;
2432
2433 /*-- Game start info cache: --*/
2434 int gs_gamenum;
2435 char gs_kind[MSG_SIZ];
2436 static char player1Name[128] = "";
2437 static char player2Name[128] = "";
2438 static char cont_seq[] = "\n\\   ";
2439 static int player1Rating = -1;
2440 static int player2Rating = -1;
2441 /*----------------------------*/
2442
2443 ColorClass curColor = ColorNormal;
2444 int suppressKibitz = 0;
2445
2446 // [HGM] seekgraph
2447 Boolean soughtPending = FALSE;
2448 Boolean seekGraphUp;
2449 #define MAX_SEEK_ADS 200
2450 #define SQUARE 0x80
2451 char *seekAdList[MAX_SEEK_ADS];
2452 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2453 float tcList[MAX_SEEK_ADS];
2454 char colorList[MAX_SEEK_ADS];
2455 int nrOfSeekAds = 0;
2456 int minRating = 1010, maxRating = 2800;
2457 int hMargin = 10, vMargin = 20, h, w;
2458 extern int squareSize, lineGap;
2459
2460 void
2461 PlotSeekAd(int i)
2462 {
2463         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2464         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2465         if(r < minRating+100 && r >=0 ) r = minRating+100;
2466         if(r > maxRating) r = maxRating;
2467         if(tc < 1.) tc = 1.;
2468         if(tc > 95.) tc = 95.;
2469         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2470         y = ((double)r - minRating)/(maxRating - minRating)
2471             * (h-vMargin-squareSize/8-1) + vMargin;
2472         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2473         if(strstr(seekAdList[i], " u ")) color = 1;
2474         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2475            !strstr(seekAdList[i], "bullet") &&
2476            !strstr(seekAdList[i], "blitz") &&
2477            !strstr(seekAdList[i], "standard") ) color = 2;
2478         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2479         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2480 }
2481
2482 void
2483 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2484 {
2485         char buf[MSG_SIZ], *ext = "";
2486         VariantClass v = StringToVariant(type);
2487         if(strstr(type, "wild")) {
2488             ext = type + 4; // append wild number
2489             if(v == VariantFischeRandom) type = "chess960"; else
2490             if(v == VariantLoadable) type = "setup"; else
2491             type = VariantName(v);
2492         }
2493         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2494         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2495             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2496             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2497             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2498             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2499             seekNrList[nrOfSeekAds] = nr;
2500             zList[nrOfSeekAds] = 0;
2501             seekAdList[nrOfSeekAds++] = StrSave(buf);
2502             if(plot) PlotSeekAd(nrOfSeekAds-1);
2503         }
2504 }
2505
2506 void
2507 EraseSeekDot(int i)
2508 {
2509     int x = xList[i], y = yList[i], d=squareSize/4, k;
2510     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2511     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2512     // now replot every dot that overlapped
2513     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2514         int xx = xList[k], yy = yList[k];
2515         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2516             DrawSeekDot(xx, yy, colorList[k]);
2517     }
2518 }
2519
2520 void
2521 RemoveSeekAd(int nr)
2522 {
2523         int i;
2524         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2525             EraseSeekDot(i);
2526             if(seekAdList[i]) free(seekAdList[i]);
2527             seekAdList[i] = seekAdList[--nrOfSeekAds];
2528             seekNrList[i] = seekNrList[nrOfSeekAds];
2529             ratingList[i] = ratingList[nrOfSeekAds];
2530             colorList[i]  = colorList[nrOfSeekAds];
2531             tcList[i] = tcList[nrOfSeekAds];
2532             xList[i]  = xList[nrOfSeekAds];
2533             yList[i]  = yList[nrOfSeekAds];
2534             zList[i]  = zList[nrOfSeekAds];
2535             seekAdList[nrOfSeekAds] = NULL;
2536             break;
2537         }
2538 }
2539
2540 Boolean
2541 MatchSoughtLine(char *line)
2542 {
2543     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2544     int nr, base, inc, u=0; char dummy;
2545
2546     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2547        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2548        (u=1) &&
2549        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2550         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2551         // match: compact and save the line
2552         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2553         return TRUE;
2554     }
2555     return FALSE;
2556 }
2557
2558 int
2559 DrawSeekGraph()
2560 {
2561     int i;
2562     if(!seekGraphUp) return FALSE;
2563     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2564     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2565
2566     DrawSeekBackground(0, 0, w, h);
2567     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2568     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2569     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2570         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2571         yy = h-1-yy;
2572         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2573         if(i%500 == 0) {
2574             char buf[MSG_SIZ];
2575             snprintf(buf, MSG_SIZ, "%d", i);
2576             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2577         }
2578     }
2579     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2580     for(i=1; i<100; i+=(i<10?1:5)) {
2581         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2582         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2583         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2584             char buf[MSG_SIZ];
2585             snprintf(buf, MSG_SIZ, "%d", i);
2586             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2587         }
2588     }
2589     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2590     return TRUE;
2591 }
2592
2593 int SeekGraphClick(ClickType click, int x, int y, int moving)
2594 {
2595     static int lastDown = 0, displayed = 0, lastSecond;
2596     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2597         if(click == Release || moving) return FALSE;
2598         nrOfSeekAds = 0;
2599         soughtPending = TRUE;
2600         SendToICS(ics_prefix);
2601         SendToICS("sought\n"); // should this be "sought all"?
2602     } else { // issue challenge based on clicked ad
2603         int dist = 10000; int i, closest = 0, second = 0;
2604         for(i=0; i<nrOfSeekAds; i++) {
2605             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2606             if(d < dist) { dist = d; closest = i; }
2607             second += (d - zList[i] < 120); // count in-range ads
2608             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2609         }
2610         if(dist < 120) {
2611             char buf[MSG_SIZ];
2612             second = (second > 1);
2613             if(displayed != closest || second != lastSecond) {
2614                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2615                 lastSecond = second; displayed = closest;
2616             }
2617             if(click == Press) {
2618                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2619                 lastDown = closest;
2620                 return TRUE;
2621             } // on press 'hit', only show info
2622             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2623             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2624             SendToICS(ics_prefix);
2625             SendToICS(buf);
2626             return TRUE; // let incoming board of started game pop down the graph
2627         } else if(click == Release) { // release 'miss' is ignored
2628             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2629             if(moving == 2) { // right up-click
2630                 nrOfSeekAds = 0; // refresh graph
2631                 soughtPending = TRUE;
2632                 SendToICS(ics_prefix);
2633                 SendToICS("sought\n"); // should this be "sought all"?
2634             }
2635             return TRUE;
2636         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2637         // press miss or release hit 'pop down' seek graph
2638         seekGraphUp = FALSE;
2639         DrawPosition(TRUE, NULL);
2640     }
2641     return TRUE;
2642 }
2643
2644 void
2645 read_from_ics(isr, closure, data, count, error)
2646      InputSourceRef isr;
2647      VOIDSTAR closure;
2648      char *data;
2649      int count;
2650      int error;
2651 {
2652 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2653 #define STARTED_NONE 0
2654 #define STARTED_MOVES 1
2655 #define STARTED_BOARD 2
2656 #define STARTED_OBSERVE 3
2657 #define STARTED_HOLDINGS 4
2658 #define STARTED_CHATTER 5
2659 #define STARTED_COMMENT 6
2660 #define STARTED_MOVES_NOHIDE 7
2661
2662     static int started = STARTED_NONE;
2663     static char parse[20000];
2664     static int parse_pos = 0;
2665     static char buf[BUF_SIZE + 1];
2666     static int firstTime = TRUE, intfSet = FALSE;
2667     static ColorClass prevColor = ColorNormal;
2668     static int savingComment = FALSE;
2669     static int cmatch = 0; // continuation sequence match
2670     char *bp;
2671     char str[MSG_SIZ];
2672     int i, oldi;
2673     int buf_len;
2674     int next_out;
2675     int tkind;
2676     int backup;    /* [DM] For zippy color lines */
2677     char *p;
2678     char talker[MSG_SIZ]; // [HGM] chat
2679     int channel;
2680
2681     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2682
2683     if (appData.debugMode) {
2684       if (!error) {
2685         fprintf(debugFP, "<ICS: ");
2686         show_bytes(debugFP, data, count);
2687         fprintf(debugFP, "\n");
2688       }
2689     }
2690
2691     if (appData.debugMode) { int f = forwardMostMove;
2692         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2693                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2694                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2695     }
2696     if (count > 0) {
2697         /* If last read ended with a partial line that we couldn't parse,
2698            prepend it to the new read and try again. */
2699         if (leftover_len > 0) {
2700             for (i=0; i<leftover_len; i++)
2701               buf[i] = buf[leftover_start + i];
2702         }
2703
2704     /* copy new characters into the buffer */
2705     bp = buf + leftover_len;
2706     buf_len=leftover_len;
2707     for (i=0; i<count; i++)
2708     {
2709         // ignore these
2710         if (data[i] == '\r')
2711             continue;
2712
2713         // join lines split by ICS?
2714         if (!appData.noJoin)
2715         {
2716             /*
2717                 Joining just consists of finding matches against the
2718                 continuation sequence, and discarding that sequence
2719                 if found instead of copying it.  So, until a match
2720                 fails, there's nothing to do since it might be the
2721                 complete sequence, and thus, something we don't want
2722                 copied.
2723             */
2724             if (data[i] == cont_seq[cmatch])
2725             {
2726                 cmatch++;
2727                 if (cmatch == strlen(cont_seq))
2728                 {
2729                     cmatch = 0; // complete match.  just reset the counter
2730
2731                     /*
2732                         it's possible for the ICS to not include the space
2733                         at the end of the last word, making our [correct]
2734                         join operation fuse two separate words.  the server
2735                         does this when the space occurs at the width setting.
2736                     */
2737                     if (!buf_len || buf[buf_len-1] != ' ')
2738                     {
2739                         *bp++ = ' ';
2740                         buf_len++;
2741                     }
2742                 }
2743                 continue;
2744             }
2745             else if (cmatch)
2746             {
2747                 /*
2748                     match failed, so we have to copy what matched before
2749                     falling through and copying this character.  In reality,
2750                     this will only ever be just the newline character, but
2751                     it doesn't hurt to be precise.
2752                 */
2753                 strncpy(bp, cont_seq, cmatch);
2754                 bp += cmatch;
2755                 buf_len += cmatch;
2756                 cmatch = 0;
2757             }
2758         }
2759
2760         // copy this char
2761         *bp++ = data[i];
2762         buf_len++;
2763     }
2764
2765         buf[buf_len] = NULLCHAR;
2766 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2767         next_out = 0;
2768         leftover_start = 0;
2769
2770         i = 0;
2771         while (i < buf_len) {
2772             /* Deal with part of the TELNET option negotiation
2773                protocol.  We refuse to do anything beyond the
2774                defaults, except that we allow the WILL ECHO option,
2775                which ICS uses to turn off password echoing when we are
2776                directly connected to it.  We reject this option
2777                if localLineEditing mode is on (always on in xboard)
2778                and we are talking to port 23, which might be a real
2779                telnet server that will try to keep WILL ECHO on permanently.
2780              */
2781             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2782                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2783                 unsigned char option;
2784                 oldi = i;
2785                 switch ((unsigned char) buf[++i]) {
2786                   case TN_WILL:
2787                     if (appData.debugMode)
2788                       fprintf(debugFP, "\n<WILL ");
2789                     switch (option = (unsigned char) buf[++i]) {
2790                       case TN_ECHO:
2791                         if (appData.debugMode)
2792                           fprintf(debugFP, "ECHO ");
2793                         /* Reply only if this is a change, according
2794                            to the protocol rules. */
2795                         if (remoteEchoOption) break;
2796                         if (appData.localLineEditing &&
2797                             atoi(appData.icsPort) == TN_PORT) {
2798                             TelnetRequest(TN_DONT, TN_ECHO);
2799                         } else {
2800                             EchoOff();
2801                             TelnetRequest(TN_DO, TN_ECHO);
2802                             remoteEchoOption = TRUE;
2803                         }
2804                         break;
2805                       default:
2806                         if (appData.debugMode)
2807                           fprintf(debugFP, "%d ", option);
2808                         /* Whatever this is, we don't want it. */
2809                         TelnetRequest(TN_DONT, option);
2810                         break;
2811                     }
2812                     break;
2813                   case TN_WONT:
2814                     if (appData.debugMode)
2815                       fprintf(debugFP, "\n<WONT ");
2816                     switch (option = (unsigned char) buf[++i]) {
2817                       case TN_ECHO:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "ECHO ");
2820                         /* Reply only if this is a change, according
2821                            to the protocol rules. */
2822                         if (!remoteEchoOption) break;
2823                         EchoOn();
2824                         TelnetRequest(TN_DONT, TN_ECHO);
2825                         remoteEchoOption = FALSE;
2826                         break;
2827                       default:
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "%d ", (unsigned char) option);
2830                         /* Whatever this is, it must already be turned
2831                            off, because we never agree to turn on
2832                            anything non-default, so according to the
2833                            protocol rules, we don't reply. */
2834                         break;
2835                     }
2836                     break;
2837                   case TN_DO:
2838                     if (appData.debugMode)
2839                       fprintf(debugFP, "\n<DO ");
2840                     switch (option = (unsigned char) buf[++i]) {
2841                       default:
2842                         /* Whatever this is, we refuse to do it. */
2843                         if (appData.debugMode)
2844                           fprintf(debugFP, "%d ", option);
2845                         TelnetRequest(TN_WONT, option);
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DONT:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DONT ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", option);
2856                         /* Whatever this is, we are already not doing
2857                            it, because we never agree to do anything
2858                            non-default, so according to the protocol
2859                            rules, we don't reply. */
2860                         break;
2861                     }
2862                     break;
2863                   case TN_IAC:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<IAC ");
2866                     /* Doubled IAC; pass it through */
2867                     i--;
2868                     break;
2869                   default:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2872                     /* Drop all other telnet commands on the floor */
2873                     break;
2874                 }
2875                 if (oldi > next_out)
2876                   SendToPlayer(&buf[next_out], oldi - next_out);
2877                 if (++i > next_out)
2878                   next_out = i;
2879                 continue;
2880             }
2881
2882             /* OK, this at least will *usually* work */
2883             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2884                 loggedOn = TRUE;
2885             }
2886
2887             if (loggedOn && !intfSet) {
2888                 if (ics_type == ICS_ICC) {
2889                   snprintf(str, MSG_SIZ,
2890                           "/set-quietly interface %s\n/set-quietly style 12\n",
2891                           programVersion);
2892                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2893                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2894                 } else if (ics_type == ICS_CHESSNET) {
2895                   snprintf(str, MSG_SIZ, "/style 12\n");
2896                 } else {
2897                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2898                   strcat(str, programVersion);
2899                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2900                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2901                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2902 #ifdef WIN32
2903                   strcat(str, "$iset nohighlight 1\n");
2904 #endif
2905                   strcat(str, "$iset lock 1\n$style 12\n");
2906                 }
2907                 SendToICS(str);
2908                 NotifyFrontendLogin();
2909                 intfSet = TRUE;
2910             }
2911
2912             if (started == STARTED_COMMENT) {
2913                 /* Accumulate characters in comment */
2914                 parse[parse_pos++] = buf[i];
2915                 if (buf[i] == '\n') {
2916                     parse[parse_pos] = NULLCHAR;
2917                     if(chattingPartner>=0) {
2918                         char mess[MSG_SIZ];
2919                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2920                         OutputChatMessage(chattingPartner, mess);
2921                         chattingPartner = -1;
2922                         next_out = i+1; // [HGM] suppress printing in ICS window
2923                     } else
2924                     if(!suppressKibitz) // [HGM] kibitz
2925                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2926                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2927                         int nrDigit = 0, nrAlph = 0, j;
2928                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2929                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2930                         parse[parse_pos] = NULLCHAR;
2931                         // try to be smart: if it does not look like search info, it should go to
2932                         // ICS interaction window after all, not to engine-output window.
2933                         for(j=0; j<parse_pos; j++) { // count letters and digits
2934                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2935                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2936                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2937                         }
2938                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2939                             int depth=0; float score;
2940                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2941                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2942                                 pvInfoList[forwardMostMove-1].depth = depth;
2943                                 pvInfoList[forwardMostMove-1].score = 100*score;
2944                             }
2945                             OutputKibitz(suppressKibitz, parse);
2946                         } else {
2947                             char tmp[MSG_SIZ];
2948                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2949                             SendToPlayer(tmp, strlen(tmp));
2950                         }
2951                         next_out = i+1; // [HGM] suppress printing in ICS window
2952                     }
2953                     started = STARTED_NONE;
2954                 } else {
2955                     /* Don't match patterns against characters in comment */
2956                     i++;
2957                     continue;
2958                 }
2959             }
2960             if (started == STARTED_CHATTER) {
2961                 if (buf[i] != '\n') {
2962                     /* Don't match patterns against characters in chatter */
2963                     i++;
2964                     continue;
2965                 }
2966                 started = STARTED_NONE;
2967                 if(suppressKibitz) next_out = i+1;
2968             }
2969
2970             /* Kludge to deal with rcmd protocol */
2971             if (firstTime && looking_at(buf, &i, "\001*")) {
2972                 DisplayFatalError(&buf[1], 0, 1);
2973                 continue;
2974             } else {
2975                 firstTime = FALSE;
2976             }
2977
2978             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2979                 ics_type = ICS_ICC;
2980                 ics_prefix = "/";
2981                 if (appData.debugMode)
2982                   fprintf(debugFP, "ics_type %d\n", ics_type);
2983                 continue;
2984             }
2985             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2986                 ics_type = ICS_FICS;
2987                 ics_prefix = "$";
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, "ics_type %d\n", ics_type);
2990                 continue;
2991             }
2992             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2993                 ics_type = ICS_CHESSNET;
2994                 ics_prefix = "/";
2995                 if (appData.debugMode)
2996                   fprintf(debugFP, "ics_type %d\n", ics_type);
2997                 continue;
2998             }
2999
3000             if (!loggedOn &&
3001                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3002                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3003                  looking_at(buf, &i, "will be \"*\""))) {
3004               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3005               continue;
3006             }
3007
3008             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3009               char buf[MSG_SIZ];
3010               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3011               DisplayIcsInteractionTitle(buf);
3012               have_set_title = TRUE;
3013             }
3014
3015             /* skip finger notes */
3016             if (started == STARTED_NONE &&
3017                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3018                  (buf[i] == '1' && buf[i+1] == '0')) &&
3019                 buf[i+2] == ':' && buf[i+3] == ' ') {
3020               started = STARTED_CHATTER;
3021               i += 3;
3022               continue;
3023             }
3024
3025             oldi = i;
3026             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3027             if(appData.seekGraph) {
3028                 if(soughtPending && MatchSoughtLine(buf+i)) {
3029                     i = strstr(buf+i, "rated") - buf;
3030                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3031                     next_out = leftover_start = i;
3032                     started = STARTED_CHATTER;
3033                     suppressKibitz = TRUE;
3034                     continue;
3035                 }
3036                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3037                         && looking_at(buf, &i, "* ads displayed")) {
3038                     soughtPending = FALSE;
3039                     seekGraphUp = TRUE;
3040                     DrawSeekGraph();
3041                     continue;
3042                 }
3043                 if(appData.autoRefresh) {
3044                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3045                         int s = (ics_type == ICS_ICC); // ICC format differs
3046                         if(seekGraphUp)
3047                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3048                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3049                         looking_at(buf, &i, "*% "); // eat prompt
3050                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3051                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                         next_out = i; // suppress
3053                         continue;
3054                     }
3055                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3056                         char *p = star_match[0];
3057                         while(*p) {
3058                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3059                             while(*p && *p++ != ' '); // next
3060                         }
3061                         looking_at(buf, &i, "*% "); // eat prompt
3062                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063                         next_out = i;
3064                         continue;
3065                     }
3066                 }
3067             }
3068
3069             /* skip formula vars */
3070             if (started == STARTED_NONE &&
3071                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3072               started = STARTED_CHATTER;
3073               i += 3;
3074               continue;
3075             }
3076
3077             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3078             if (appData.autoKibitz && started == STARTED_NONE &&
3079                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3080                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3081                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3082                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3083                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3084                         suppressKibitz = TRUE;
3085                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3086                         next_out = i;
3087                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3088                                 && (gameMode == IcsPlayingWhite)) ||
3089                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3090                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3091                             started = STARTED_CHATTER; // own kibitz we simply discard
3092                         else {
3093                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3094                             parse_pos = 0; parse[0] = NULLCHAR;
3095                             savingComment = TRUE;
3096                             suppressKibitz = gameMode != IcsObserving ? 2 :
3097                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3098                         }
3099                         continue;
3100                 } else
3101                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3102                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3103                          && atoi(star_match[0])) {
3104                     // suppress the acknowledgements of our own autoKibitz
3105                     char *p;
3106                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3108                     SendToPlayer(star_match[0], strlen(star_match[0]));
3109                     if(looking_at(buf, &i, "*% ")) // eat prompt
3110                         suppressKibitz = FALSE;
3111                     next_out = i;
3112                     continue;
3113                 }
3114             } // [HGM] kibitz: end of patch
3115
3116             // [HGM] chat: intercept tells by users for which we have an open chat window
3117             channel = -1;
3118             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3119                                            looking_at(buf, &i, "* whispers:") ||
3120                                            looking_at(buf, &i, "* kibitzes:") ||
3121                                            looking_at(buf, &i, "* shouts:") ||
3122                                            looking_at(buf, &i, "* c-shouts:") ||
3123                                            looking_at(buf, &i, "--> * ") ||
3124                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3125                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3126                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3127                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3128                 int p;
3129                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3130                 chattingPartner = -1;
3131
3132                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3133                 for(p=0; p<MAX_CHAT; p++) {
3134                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3135                     talker[0] = '['; strcat(talker, "] ");
3136                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3137                     chattingPartner = p; break;
3138                     }
3139                 } else
3140                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3141                 for(p=0; p<MAX_CHAT; p++) {
3142                     if(!strcmp("kibitzes", chatPartner[p])) {
3143                         talker[0] = '['; strcat(talker, "] ");
3144                         chattingPartner = p; break;
3145                     }
3146                 } else
3147                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3148                 for(p=0; p<MAX_CHAT; p++) {
3149                     if(!strcmp("whispers", chatPartner[p])) {
3150                         talker[0] = '['; strcat(talker, "] ");
3151                         chattingPartner = p; break;
3152                     }
3153                 } else
3154                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3155                   if(buf[i-8] == '-' && buf[i-3] == 't')
3156                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3157                     if(!strcmp("c-shouts", chatPartner[p])) {
3158                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3159                         chattingPartner = p; break;
3160                     }
3161                   }
3162                   if(chattingPartner < 0)
3163                   for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("shouts", chatPartner[p])) {
3165                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3166                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3167                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3168                         chattingPartner = p; break;
3169                     }
3170                   }
3171                 }
3172                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3173                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3174                     talker[0] = 0; Colorize(ColorTell, FALSE);
3175                     chattingPartner = p; break;
3176                 }
3177                 if(chattingPartner<0) i = oldi; else {
3178                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3179                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3180                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3181                     started = STARTED_COMMENT;
3182                     parse_pos = 0; parse[0] = NULLCHAR;
3183                     savingComment = 3 + chattingPartner; // counts as TRUE
3184                     suppressKibitz = TRUE;
3185                     continue;
3186                 }
3187             } // [HGM] chat: end of patch
3188
3189           backup = i;
3190             if (appData.zippyTalk || appData.zippyPlay) {
3191                 /* [DM] Backup address for color zippy lines */
3192 #if ZIPPY
3193                if (loggedOn == TRUE)
3194                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3195                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3196 #endif
3197             } // [DM] 'else { ' deleted
3198                 if (
3199                     /* Regular tells and says */
3200                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3201                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3202                     looking_at(buf, &i, "* says: ") ||
3203                     /* Don't color "message" or "messages" output */
3204                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3205                     looking_at(buf, &i, "*. * at *:*: ") ||
3206                     looking_at(buf, &i, "--* (*:*): ") ||
3207                     /* Message notifications (same color as tells) */
3208                     looking_at(buf, &i, "* has left a message ") ||
3209                     looking_at(buf, &i, "* just sent you a message:\n") ||
3210                     /* Whispers and kibitzes */
3211                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3212                     looking_at(buf, &i, "* kibitzes: ") ||
3213                     /* Channel tells */
3214                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3215
3216                   if (tkind == 1 && strchr(star_match[0], ':')) {
3217                       /* Avoid "tells you:" spoofs in channels */
3218                      tkind = 3;
3219                   }
3220                   if (star_match[0][0] == NULLCHAR ||
3221                       strchr(star_match[0], ' ') ||
3222                       (tkind == 3 && strchr(star_match[1], ' '))) {
3223                     /* Reject bogus matches */
3224                     i = oldi;
3225                   } else {
3226                     if (appData.colorize) {
3227                       if (oldi > next_out) {
3228                         SendToPlayer(&buf[next_out], oldi - next_out);
3229                         next_out = oldi;
3230                       }
3231                       switch (tkind) {
3232                       case 1:
3233                         Colorize(ColorTell, FALSE);
3234                         curColor = ColorTell;
3235                         break;
3236                       case 2:
3237                         Colorize(ColorKibitz, FALSE);
3238                         curColor = ColorKibitz;
3239                         break;
3240                       case 3:
3241                         p = strrchr(star_match[1], '(');
3242                         if (p == NULL) {
3243                           p = star_match[1];
3244                         } else {
3245                           p++;
3246                         }
3247                         if (atoi(p) == 1) {
3248                           Colorize(ColorChannel1, FALSE);
3249                           curColor = ColorChannel1;
3250                         } else {
3251                           Colorize(ColorChannel, FALSE);
3252                           curColor = ColorChannel;
3253                         }
3254                         break;
3255                       case 5:
3256                         curColor = ColorNormal;
3257                         break;
3258                       }
3259                     }
3260                     if (started == STARTED_NONE && appData.autoComment &&
3261                         (gameMode == IcsObserving ||
3262                          gameMode == IcsPlayingWhite ||
3263                          gameMode == IcsPlayingBlack)) {
3264                       parse_pos = i - oldi;
3265                       memcpy(parse, &buf[oldi], parse_pos);
3266                       parse[parse_pos] = NULLCHAR;
3267                       started = STARTED_COMMENT;
3268                       savingComment = TRUE;
3269                     } else {
3270                       started = STARTED_CHATTER;
3271                       savingComment = FALSE;
3272                     }
3273                     loggedOn = TRUE;
3274                     continue;
3275                   }
3276                 }
3277
3278                 if (looking_at(buf, &i, "* s-shouts: ") ||
3279                     looking_at(buf, &i, "* c-shouts: ")) {
3280                     if (appData.colorize) {
3281                         if (oldi > next_out) {
3282                             SendToPlayer(&buf[next_out], oldi - next_out);
3283                             next_out = oldi;
3284                         }
3285                         Colorize(ColorSShout, FALSE);
3286                         curColor = ColorSShout;
3287                     }
3288                     loggedOn = TRUE;
3289                     started = STARTED_CHATTER;
3290                     continue;
3291                 }
3292
3293                 if (looking_at(buf, &i, "--->")) {
3294                     loggedOn = TRUE;
3295                     continue;
3296                 }
3297
3298                 if (looking_at(buf, &i, "* shouts: ") ||
3299                     looking_at(buf, &i, "--> ")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorShout, FALSE);
3306                         curColor = ColorShout;
3307                     }
3308                     loggedOn = TRUE;
3309                     started = STARTED_CHATTER;
3310                     continue;
3311                 }
3312
3313                 if (looking_at( buf, &i, "Challenge:")) {
3314                     if (appData.colorize) {
3315                         if (oldi > next_out) {
3316                             SendToPlayer(&buf[next_out], oldi - next_out);
3317                             next_out = oldi;
3318                         }
3319                         Colorize(ColorChallenge, FALSE);
3320                         curColor = ColorChallenge;
3321                     }
3322                     loggedOn = TRUE;
3323                     continue;
3324                 }
3325
3326                 if (looking_at(buf, &i, "* offers you") ||
3327                     looking_at(buf, &i, "* offers to be") ||
3328                     looking_at(buf, &i, "* would like to") ||
3329                     looking_at(buf, &i, "* requests to") ||
3330                     looking_at(buf, &i, "Your opponent offers") ||
3331                     looking_at(buf, &i, "Your opponent requests")) {
3332
3333                     if (appData.colorize) {
3334                         if (oldi > next_out) {
3335                             SendToPlayer(&buf[next_out], oldi - next_out);
3336                             next_out = oldi;
3337                         }
3338                         Colorize(ColorRequest, FALSE);
3339                         curColor = ColorRequest;
3340                     }
3341                     continue;
3342                 }
3343
3344                 if (looking_at(buf, &i, "* (*) seeking")) {
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorSeek, FALSE);
3351                         curColor = ColorSeek;
3352                     }
3353                     continue;
3354             }
3355
3356           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3357
3358             if (looking_at(buf, &i, "\\   ")) {
3359                 if (prevColor != ColorNormal) {
3360                     if (oldi > next_out) {
3361                         SendToPlayer(&buf[next_out], oldi - next_out);
3362                         next_out = oldi;
3363                     }
3364                     Colorize(prevColor, TRUE);
3365                     curColor = prevColor;
3366                 }
3367                 if (savingComment) {
3368                     parse_pos = i - oldi;
3369                     memcpy(parse, &buf[oldi], parse_pos);
3370                     parse[parse_pos] = NULLCHAR;
3371                     started = STARTED_COMMENT;
3372                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3373                         chattingPartner = savingComment - 3; // kludge to remember the box
3374                 } else {
3375                     started = STARTED_CHATTER;
3376                 }
3377                 continue;
3378             }
3379
3380             if (looking_at(buf, &i, "Black Strength :") ||
3381                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3382                 looking_at(buf, &i, "<10>") ||
3383                 looking_at(buf, &i, "#@#")) {
3384                 /* Wrong board style */
3385                 loggedOn = TRUE;
3386                 SendToICS(ics_prefix);
3387                 SendToICS("set style 12\n");
3388                 SendToICS(ics_prefix);
3389                 SendToICS("refresh\n");
3390                 continue;
3391             }
3392
3393             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3394                 ICSInitScript();
3395                 have_sent_ICS_logon = 1;
3396                 continue;
3397             }
3398
3399             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3400                 (looking_at(buf, &i, "\n<12> ") ||
3401                  looking_at(buf, &i, "<12> "))) {
3402                 loggedOn = TRUE;
3403                 if (oldi > next_out) {
3404                     SendToPlayer(&buf[next_out], oldi - next_out);
3405                 }
3406                 next_out = i;
3407                 started = STARTED_BOARD;
3408                 parse_pos = 0;
3409                 continue;
3410             }
3411
3412             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3413                 looking_at(buf, &i, "<b1> ")) {
3414                 if (oldi > next_out) {
3415                     SendToPlayer(&buf[next_out], oldi - next_out);
3416                 }
3417                 next_out = i;
3418                 started = STARTED_HOLDINGS;
3419                 parse_pos = 0;
3420                 continue;
3421             }
3422
3423             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3424                 loggedOn = TRUE;
3425                 /* Header for a move list -- first line */
3426
3427                 switch (ics_getting_history) {
3428                   case H_FALSE:
3429                     switch (gameMode) {
3430                       case IcsIdle:
3431                       case BeginningOfGame:
3432                         /* User typed "moves" or "oldmoves" while we
3433                            were idle.  Pretend we asked for these
3434                            moves and soak them up so user can step
3435                            through them and/or save them.
3436                            */
3437                         Reset(FALSE, TRUE);
3438                         gameMode = IcsObserving;
3439                         ModeHighlight();
3440                         ics_gamenum = -1;
3441                         ics_getting_history = H_GOT_UNREQ_HEADER;
3442                         break;
3443                       case EditGame: /*?*/
3444                       case EditPosition: /*?*/
3445                         /* Should above feature work in these modes too? */
3446                         /* For now it doesn't */
3447                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3448                         break;
3449                       default:
3450                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3451                         break;
3452                     }
3453                     break;
3454                   case H_REQUESTED:
3455                     /* Is this the right one? */
3456                     if (gameInfo.white && gameInfo.black &&
3457                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3458                         strcmp(gameInfo.black, star_match[2]) == 0) {
3459                         /* All is well */
3460                         ics_getting_history = H_GOT_REQ_HEADER;
3461                     }
3462                     break;
3463                   case H_GOT_REQ_HEADER:
3464                   case H_GOT_UNREQ_HEADER:
3465                   case H_GOT_UNWANTED_HEADER:
3466                   case H_GETTING_MOVES:
3467                     /* Should not happen */
3468                     DisplayError(_("Error gathering move list: two headers"), 0);
3469                     ics_getting_history = H_FALSE;
3470                     break;
3471                 }
3472
3473                 /* Save player ratings into gameInfo if needed */
3474                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3475                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3476                     (gameInfo.whiteRating == -1 ||
3477                      gameInfo.blackRating == -1)) {
3478
3479                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3480                     gameInfo.blackRating = string_to_rating(star_match[3]);
3481                     if (appData.debugMode)
3482                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3483                               gameInfo.whiteRating, gameInfo.blackRating);
3484                 }
3485                 continue;
3486             }
3487
3488             if (looking_at(buf, &i,
3489               "* * match, initial time: * minute*, increment: * second")) {
3490                 /* Header for a move list -- second line */
3491                 /* Initial board will follow if this is a wild game */
3492                 if (gameInfo.event != NULL) free(gameInfo.event);
3493                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3494                 gameInfo.event = StrSave(str);
3495                 /* [HGM] we switched variant. Translate boards if needed. */
3496                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "Move  ")) {
3501                 /* Beginning of a move list */
3502                 switch (ics_getting_history) {
3503                   case H_FALSE:
3504                     /* Normally should not happen */
3505                     /* Maybe user hit reset while we were parsing */
3506                     break;
3507                   case H_REQUESTED:
3508                     /* Happens if we are ignoring a move list that is not
3509                      * the one we just requested.  Common if the user
3510                      * tries to observe two games without turning off
3511                      * getMoveList */
3512                     break;
3513                   case H_GETTING_MOVES:
3514                     /* Should not happen */
3515                     DisplayError(_("Error gathering move list: nested"), 0);
3516                     ics_getting_history = H_FALSE;
3517                     break;
3518                   case H_GOT_REQ_HEADER:
3519                     ics_getting_history = H_GETTING_MOVES;
3520                     started = STARTED_MOVES;
3521                     parse_pos = 0;
3522                     if (oldi > next_out) {
3523                         SendToPlayer(&buf[next_out], oldi - next_out);
3524                     }
3525                     break;
3526                   case H_GOT_UNREQ_HEADER:
3527                     ics_getting_history = H_GETTING_MOVES;
3528                     started = STARTED_MOVES_NOHIDE;
3529                     parse_pos = 0;
3530                     break;
3531                   case H_GOT_UNWANTED_HEADER:
3532                     ics_getting_history = H_FALSE;
3533                     break;
3534                 }
3535                 continue;
3536             }
3537
3538             if (looking_at(buf, &i, "% ") ||
3539                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3540                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3541                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3542                     soughtPending = FALSE;
3543                     seekGraphUp = TRUE;
3544                     DrawSeekGraph();
3545                 }
3546                 if(suppressKibitz) next_out = i;
3547                 savingComment = FALSE;
3548                 suppressKibitz = 0;
3549                 switch (started) {
3550                   case STARTED_MOVES:
3551                   case STARTED_MOVES_NOHIDE:
3552                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3553                     parse[parse_pos + i - oldi] = NULLCHAR;
3554                     ParseGameHistory(parse);
3555 #if ZIPPY
3556                     if (appData.zippyPlay && first.initDone) {
3557                         FeedMovesToProgram(&first, forwardMostMove);
3558                         if (gameMode == IcsPlayingWhite) {
3559                             if (WhiteOnMove(forwardMostMove)) {
3560                                 if (first.sendTime) {
3561                                   if (first.useColors) {
3562                                     SendToProgram("black\n", &first);
3563                                   }
3564                                   SendTimeRemaining(&first, TRUE);
3565                                 }
3566                                 if (first.useColors) {
3567                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3568                                 }
3569                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3570                                 first.maybeThinking = TRUE;
3571                             } else {
3572                                 if (first.usePlayother) {
3573                                   if (first.sendTime) {
3574                                     SendTimeRemaining(&first, TRUE);
3575                                   }
3576                                   SendToProgram("playother\n", &first);
3577                                   firstMove = FALSE;
3578                                 } else {
3579                                   firstMove = TRUE;
3580                                 }
3581                             }
3582                         } else if (gameMode == IcsPlayingBlack) {
3583                             if (!WhiteOnMove(forwardMostMove)) {
3584                                 if (first.sendTime) {
3585                                   if (first.useColors) {
3586                                     SendToProgram("white\n", &first);
3587                                   }
3588                                   SendTimeRemaining(&first, FALSE);
3589                                 }
3590                                 if (first.useColors) {
3591                                   SendToProgram("black\n", &first);
3592                                 }
3593                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3594                                 first.maybeThinking = TRUE;
3595                             } else {
3596                                 if (first.usePlayother) {
3597                                   if (first.sendTime) {
3598                                     SendTimeRemaining(&first, FALSE);
3599                                   }
3600                                   SendToProgram("playother\n", &first);
3601                                   firstMove = FALSE;
3602                                 } else {
3603                                   firstMove = TRUE;
3604                                 }
3605                             }
3606                         }
3607                     }
3608 #endif
3609                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3610                         /* Moves came from oldmoves or moves command
3611                            while we weren't doing anything else.
3612                            */
3613                         currentMove = forwardMostMove;
3614                         ClearHighlights();/*!!could figure this out*/
3615                         flipView = appData.flipView;
3616                         DrawPosition(TRUE, boards[currentMove]);
3617                         DisplayBothClocks();
3618                         snprintf(str, MSG_SIZ, "%s vs. %s",
3619                                 gameInfo.white, gameInfo.black);
3620                         DisplayTitle(str);
3621                         gameMode = IcsIdle;
3622                     } else {
3623                         /* Moves were history of an active game */
3624                         if (gameInfo.resultDetails != NULL) {
3625                             free(gameInfo.resultDetails);
3626                             gameInfo.resultDetails = NULL;
3627                         }
3628                     }
3629                     HistorySet(parseList, backwardMostMove,
3630                                forwardMostMove, currentMove-1);
3631                     DisplayMove(currentMove - 1);
3632                     if (started == STARTED_MOVES) next_out = i;
3633                     started = STARTED_NONE;
3634                     ics_getting_history = H_FALSE;
3635                     break;
3636
3637                   case STARTED_OBSERVE:
3638                     started = STARTED_NONE;
3639                     SendToICS(ics_prefix);
3640                     SendToICS("refresh\n");
3641                     break;
3642
3643                   default:
3644                     break;
3645                 }
3646                 if(bookHit) { // [HGM] book: simulate book reply
3647                     static char bookMove[MSG_SIZ]; // a bit generous?
3648
3649                     programStats.nodes = programStats.depth = programStats.time =
3650                     programStats.score = programStats.got_only_move = 0;
3651                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3652
3653                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3654                     strcat(bookMove, bookHit);
3655                     HandleMachineMove(bookMove, &first);
3656                 }
3657                 continue;
3658             }
3659
3660             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3661                  started == STARTED_HOLDINGS ||
3662                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3663                 /* Accumulate characters in move list or board */
3664                 parse[parse_pos++] = buf[i];
3665             }
3666
3667             /* Start of game messages.  Mostly we detect start of game
3668                when the first board image arrives.  On some versions
3669                of the ICS, though, we need to do a "refresh" after starting
3670                to observe in order to get the current board right away. */
3671             if (looking_at(buf, &i, "Adding game * to observation list")) {
3672                 started = STARTED_OBSERVE;
3673                 continue;
3674             }
3675
3676             /* Handle auto-observe */
3677             if (appData.autoObserve &&
3678                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3679                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3680                 char *player;
3681                 /* Choose the player that was highlighted, if any. */
3682                 if (star_match[0][0] == '\033' ||
3683                     star_match[1][0] != '\033') {
3684                     player = star_match[0];
3685                 } else {
3686                     player = star_match[2];
3687                 }
3688                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3689                         ics_prefix, StripHighlightAndTitle(player));
3690                 SendToICS(str);
3691
3692                 /* Save ratings from notify string */
3693                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3694                 player1Rating = string_to_rating(star_match[1]);
3695                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3696                 player2Rating = string_to_rating(star_match[3]);
3697
3698                 if (appData.debugMode)
3699                   fprintf(debugFP,
3700                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3701                           player1Name, player1Rating,
3702                           player2Name, player2Rating);
3703
3704                 continue;
3705             }
3706
3707             /* Deal with automatic examine mode after a game,
3708                and with IcsObserving -> IcsExamining transition */
3709             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3710                 looking_at(buf, &i, "has made you an examiner of game *")) {
3711
3712                 int gamenum = atoi(star_match[0]);
3713                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3714                     gamenum == ics_gamenum) {
3715                     /* We were already playing or observing this game;
3716                        no need to refetch history */
3717                     gameMode = IcsExamining;
3718                     if (pausing) {
3719                         pauseExamForwardMostMove = forwardMostMove;
3720                     } else if (currentMove < forwardMostMove) {
3721                         ForwardInner(forwardMostMove);
3722                     }
3723                 } else {
3724                     /* I don't think this case really can happen */
3725                     SendToICS(ics_prefix);
3726                     SendToICS("refresh\n");
3727                 }
3728                 continue;
3729             }
3730
3731             /* Error messages */
3732 //          if (ics_user_moved) {
3733             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3734                 if (looking_at(buf, &i, "Illegal move") ||
3735                     looking_at(buf, &i, "Not a legal move") ||
3736                     looking_at(buf, &i, "Your king is in check") ||
3737                     looking_at(buf, &i, "It isn't your turn") ||
3738                     looking_at(buf, &i, "It is not your move")) {
3739                     /* Illegal move */
3740                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3741                         currentMove = forwardMostMove-1;
3742                         DisplayMove(currentMove - 1); /* before DMError */
3743                         DrawPosition(FALSE, boards[currentMove]);
3744                         SwitchClocks(forwardMostMove-1); // [HGM] race
3745                         DisplayBothClocks();
3746                     }
3747                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3748                     ics_user_moved = 0;
3749                     continue;
3750                 }
3751             }
3752
3753             if (looking_at(buf, &i, "still have time") ||
3754                 looking_at(buf, &i, "not out of time") ||
3755                 looking_at(buf, &i, "either player is out of time") ||
3756                 looking_at(buf, &i, "has timeseal; checking")) {
3757                 /* We must have called his flag a little too soon */
3758                 whiteFlag = blackFlag = FALSE;
3759                 continue;
3760             }
3761
3762             if (looking_at(buf, &i, "added * seconds to") ||
3763                 looking_at(buf, &i, "seconds were added to")) {
3764                 /* Update the clocks */
3765                 SendToICS(ics_prefix);
3766                 SendToICS("refresh\n");
3767                 continue;
3768             }
3769
3770             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3771                 ics_clock_paused = TRUE;
3772                 StopClocks();
3773                 continue;
3774             }
3775
3776             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3777                 ics_clock_paused = FALSE;
3778                 StartClocks();
3779                 continue;
3780             }
3781
3782             /* Grab player ratings from the Creating: message.
3783                Note we have to check for the special case when
3784                the ICS inserts things like [white] or [black]. */
3785             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3786                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3787                 /* star_matches:
3788                    0    player 1 name (not necessarily white)
3789                    1    player 1 rating
3790                    2    empty, white, or black (IGNORED)
3791                    3    player 2 name (not necessarily black)
3792                    4    player 2 rating
3793
3794                    The names/ratings are sorted out when the game
3795                    actually starts (below).
3796                 */
3797                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3798                 player1Rating = string_to_rating(star_match[1]);
3799                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3800                 player2Rating = string_to_rating(star_match[4]);
3801
3802                 if (appData.debugMode)
3803                   fprintf(debugFP,
3804                           "Ratings from 'Creating:' %s %d, %s %d\n",
3805                           player1Name, player1Rating,
3806                           player2Name, player2Rating);
3807
3808                 continue;
3809             }
3810
3811             /* Improved generic start/end-of-game messages */
3812             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3813                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3814                 /* If tkind == 0: */
3815                 /* star_match[0] is the game number */
3816                 /*           [1] is the white player's name */
3817                 /*           [2] is the black player's name */
3818                 /* For end-of-game: */
3819                 /*           [3] is the reason for the game end */
3820                 /*           [4] is a PGN end game-token, preceded by " " */
3821                 /* For start-of-game: */
3822                 /*           [3] begins with "Creating" or "Continuing" */
3823                 /*           [4] is " *" or empty (don't care). */
3824                 int gamenum = atoi(star_match[0]);
3825                 char *whitename, *blackname, *why, *endtoken;
3826                 ChessMove endtype = EndOfFile;
3827
3828                 if (tkind == 0) {
3829                   whitename = star_match[1];
3830                   blackname = star_match[2];
3831                   why = star_match[3];
3832                   endtoken = star_match[4];
3833                 } else {
3834                   whitename = star_match[1];
3835                   blackname = star_match[3];
3836                   why = star_match[5];
3837                   endtoken = star_match[6];
3838                 }
3839
3840                 /* Game start messages */
3841                 if (strncmp(why, "Creating ", 9) == 0 ||
3842                     strncmp(why, "Continuing ", 11) == 0) {
3843                     gs_gamenum = gamenum;
3844                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3845                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3846 #if ZIPPY
3847                     if (appData.zippyPlay) {
3848                         ZippyGameStart(whitename, blackname);
3849                     }
3850 #endif /*ZIPPY*/
3851                     partnerBoardValid = FALSE; // [HGM] bughouse
3852                     continue;
3853                 }
3854
3855                 /* Game end messages */
3856                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3857                     ics_gamenum != gamenum) {
3858                     continue;
3859                 }
3860                 while (endtoken[0] == ' ') endtoken++;
3861                 switch (endtoken[0]) {
3862                   case '*':
3863                   default:
3864                     endtype = GameUnfinished;
3865                     break;
3866                   case '0':
3867                     endtype = BlackWins;
3868                     break;
3869                   case '1':
3870                     if (endtoken[1] == '/')
3871                       endtype = GameIsDrawn;
3872                     else
3873                       endtype = WhiteWins;
3874                     break;
3875                 }
3876                 GameEnds(endtype, why, GE_ICS);
3877 #if ZIPPY
3878                 if (appData.zippyPlay && first.initDone) {
3879                     ZippyGameEnd(endtype, why);
3880                     if (first.pr == NULL) {
3881                       /* Start the next process early so that we'll
3882                          be ready for the next challenge */
3883                       StartChessProgram(&first);
3884                     }
3885                     /* Send "new" early, in case this command takes
3886                        a long time to finish, so that we'll be ready
3887                        for the next challenge. */
3888                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3889                     Reset(TRUE, TRUE);
3890                 }
3891 #endif /*ZIPPY*/
3892                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3893                 continue;
3894             }
3895
3896             if (looking_at(buf, &i, "Removing game * from observation") ||
3897                 looking_at(buf, &i, "no longer observing game *") ||
3898                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3899                 if (gameMode == IcsObserving &&
3900                     atoi(star_match[0]) == ics_gamenum)
3901                   {
3902                       /* icsEngineAnalyze */
3903                       if (appData.icsEngineAnalyze) {
3904                             ExitAnalyzeMode();
3905                             ModeHighlight();
3906                       }
3907                       StopClocks();
3908                       gameMode = IcsIdle;
3909                       ics_gamenum = -1;
3910                       ics_user_moved = FALSE;
3911                   }
3912                 continue;
3913             }
3914
3915             if (looking_at(buf, &i, "no longer examining game *")) {
3916                 if (gameMode == IcsExamining &&
3917                     atoi(star_match[0]) == ics_gamenum)
3918                   {
3919                       gameMode = IcsIdle;
3920                       ics_gamenum = -1;
3921                       ics_user_moved = FALSE;
3922                   }
3923                 continue;
3924             }
3925
3926             /* Advance leftover_start past any newlines we find,
3927                so only partial lines can get reparsed */
3928             if (looking_at(buf, &i, "\n")) {
3929                 prevColor = curColor;
3930                 if (curColor != ColorNormal) {
3931                     if (oldi > next_out) {
3932                         SendToPlayer(&buf[next_out], oldi - next_out);
3933                         next_out = oldi;
3934                     }
3935                     Colorize(ColorNormal, FALSE);
3936                     curColor = ColorNormal;
3937                 }
3938                 if (started == STARTED_BOARD) {
3939                     started = STARTED_NONE;
3940                     parse[parse_pos] = NULLCHAR;
3941                     ParseBoard12(parse);
3942                     ics_user_moved = 0;
3943
3944                     /* Send premove here */
3945                     if (appData.premove) {
3946                       char str[MSG_SIZ];
3947                       if (currentMove == 0 &&
3948                           gameMode == IcsPlayingWhite &&
3949                           appData.premoveWhite) {
3950                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3951                         if (appData.debugMode)
3952                           fprintf(debugFP, "Sending premove:\n");
3953                         SendToICS(str);
3954                       } else if (currentMove == 1 &&
3955                                  gameMode == IcsPlayingBlack &&
3956                                  appData.premoveBlack) {
3957                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3958                         if (appData.debugMode)
3959                           fprintf(debugFP, "Sending premove:\n");
3960                         SendToICS(str);
3961                       } else if (gotPremove) {
3962                         gotPremove = 0;
3963                         ClearPremoveHighlights();
3964                         if (appData.debugMode)
3965                           fprintf(debugFP, "Sending premove:\n");
3966                           UserMoveEvent(premoveFromX, premoveFromY,
3967                                         premoveToX, premoveToY,
3968                                         premovePromoChar);
3969                       }
3970                     }
3971
3972                     /* Usually suppress following prompt */
3973                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3974                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3975                         if (looking_at(buf, &i, "*% ")) {
3976                             savingComment = FALSE;
3977                             suppressKibitz = 0;
3978                         }
3979                     }
3980                     next_out = i;
3981                 } else if (started == STARTED_HOLDINGS) {
3982                     int gamenum;
3983                     char new_piece[MSG_SIZ];
3984                     started = STARTED_NONE;
3985                     parse[parse_pos] = NULLCHAR;
3986                     if (appData.debugMode)
3987                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3988                                                         parse, currentMove);
3989                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3990                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3991                         if (gameInfo.variant == VariantNormal) {
3992                           /* [HGM] We seem to switch variant during a game!
3993                            * Presumably no holdings were displayed, so we have
3994                            * to move the position two files to the right to
3995                            * create room for them!
3996                            */
3997                           VariantClass newVariant;
3998                           switch(gameInfo.boardWidth) { // base guess on board width
3999                                 case 9:  newVariant = VariantShogi; break;
4000                                 case 10: newVariant = VariantGreat; break;
4001                                 default: newVariant = VariantCrazyhouse; break;
4002                           }
4003                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4004                           /* Get a move list just to see the header, which
4005                              will tell us whether this is really bug or zh */
4006                           if (ics_getting_history == H_FALSE) {
4007                             ics_getting_history = H_REQUESTED;
4008                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4009                             SendToICS(str);
4010                           }
4011                         }
4012                         new_piece[0] = NULLCHAR;
4013                         sscanf(parse, "game %d white [%s black [%s <- %s",
4014                                &gamenum, white_holding, black_holding,
4015                                new_piece);
4016                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4017                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4018                         /* [HGM] copy holdings to board holdings area */
4019                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4020                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4021                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4022 #if ZIPPY
4023                         if (appData.zippyPlay && first.initDone) {
4024                             ZippyHoldings(white_holding, black_holding,
4025                                           new_piece);
4026                         }
4027 #endif /*ZIPPY*/
4028                         if (tinyLayout || smallLayout) {
4029                             char wh[16], bh[16];
4030                             PackHolding(wh, white_holding);
4031                             PackHolding(bh, black_holding);
4032                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4033                                     gameInfo.white, gameInfo.black);
4034                         } else {
4035                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4036                                     gameInfo.white, white_holding,
4037                                     gameInfo.black, black_holding);
4038                         }
4039                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4040                         DrawPosition(FALSE, boards[currentMove]);
4041                         DisplayTitle(str);
4042                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4043                         sscanf(parse, "game %d white [%s black [%s <- %s",
4044                                &gamenum, white_holding, black_holding,
4045                                new_piece);
4046                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4047                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4048                         /* [HGM] copy holdings to partner-board holdings area */
4049                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4050                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4051                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4052                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4053                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4054                       }
4055                     }
4056                     /* Suppress following prompt */
4057                     if (looking_at(buf, &i, "*% ")) {
4058                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4059                         savingComment = FALSE;
4060                         suppressKibitz = 0;
4061                     }
4062                     next_out = i;
4063                 }
4064                 continue;
4065             }
4066
4067             i++;                /* skip unparsed character and loop back */
4068         }
4069
4070         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4071 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4072 //          SendToPlayer(&buf[next_out], i - next_out);
4073             started != STARTED_HOLDINGS && leftover_start > next_out) {
4074             SendToPlayer(&buf[next_out], leftover_start - next_out);
4075             next_out = i;
4076         }
4077
4078         leftover_len = buf_len - leftover_start;
4079         /* if buffer ends with something we couldn't parse,
4080            reparse it after appending the next read */
4081
4082     } else if (count == 0) {
4083         RemoveInputSource(isr);
4084         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4085     } else {
4086         DisplayFatalError(_("Error reading from ICS"), error, 1);
4087     }
4088 }
4089
4090
4091 /* Board style 12 looks like this:
4092
4093    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4094
4095  * The "<12> " is stripped before it gets to this routine.  The two
4096  * trailing 0's (flip state and clock ticking) are later addition, and
4097  * some chess servers may not have them, or may have only the first.
4098  * Additional trailing fields may be added in the future.
4099  */
4100
4101 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4102
4103 #define RELATION_OBSERVING_PLAYED    0
4104 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4105 #define RELATION_PLAYING_MYMOVE      1
4106 #define RELATION_PLAYING_NOTMYMOVE  -1
4107 #define RELATION_EXAMINING           2
4108 #define RELATION_ISOLATED_BOARD     -3
4109 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4110
4111 void
4112 ParseBoard12(string)
4113      char *string;
4114 {
4115     GameMode newGameMode;
4116     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4117     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4118     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4119     char to_play, board_chars[200];
4120     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4121     char black[32], white[32];
4122     Board board;
4123     int prevMove = currentMove;
4124     int ticking = 2;
4125     ChessMove moveType;
4126     int fromX, fromY, toX, toY;
4127     char promoChar;
4128     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4129     char *bookHit = NULL; // [HGM] book
4130     Boolean weird = FALSE, reqFlag = FALSE;
4131
4132     fromX = fromY = toX = toY = -1;
4133
4134     newGame = FALSE;
4135
4136     if (appData.debugMode)
4137       fprintf(debugFP, _("Parsing board: %s\n"), string);
4138
4139     move_str[0] = NULLCHAR;
4140     elapsed_time[0] = NULLCHAR;
4141     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4142         int  i = 0, j;
4143         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4144             if(string[i] == ' ') { ranks++; files = 0; }
4145             else files++;
4146             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4147             i++;
4148         }
4149         for(j = 0; j <i; j++) board_chars[j] = string[j];
4150         board_chars[i] = '\0';
4151         string += i + 1;
4152     }
4153     n = sscanf(string, PATTERN, &to_play, &double_push,
4154                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4155                &gamenum, white, black, &relation, &basetime, &increment,
4156                &white_stren, &black_stren, &white_time, &black_time,
4157                &moveNum, str, elapsed_time, move_str, &ics_flip,
4158                &ticking);
4159
4160     if (n < 21) {
4161         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4162         DisplayError(str, 0);
4163         return;
4164     }
4165
4166     /* Convert the move number to internal form */
4167     moveNum = (moveNum - 1) * 2;
4168     if (to_play == 'B') moveNum++;
4169     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4170       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4171                         0, 1);
4172       return;
4173     }
4174
4175     switch (relation) {
4176       case RELATION_OBSERVING_PLAYED:
4177       case RELATION_OBSERVING_STATIC:
4178         if (gamenum == -1) {
4179             /* Old ICC buglet */
4180             relation = RELATION_OBSERVING_STATIC;
4181         }
4182         newGameMode = IcsObserving;
4183         break;
4184       case RELATION_PLAYING_MYMOVE:
4185       case RELATION_PLAYING_NOTMYMOVE:
4186         newGameMode =
4187           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4188             IcsPlayingWhite : IcsPlayingBlack;
4189         break;
4190       case RELATION_EXAMINING:
4191         newGameMode = IcsExamining;
4192         break;
4193       case RELATION_ISOLATED_BOARD:
4194       default:
4195         /* Just display this board.  If user was doing something else,
4196            we will forget about it until the next board comes. */
4197         newGameMode = IcsIdle;
4198         break;
4199       case RELATION_STARTING_POSITION:
4200         newGameMode = gameMode;
4201         break;
4202     }
4203
4204     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4205          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4206       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4207       char *toSqr;
4208       for (k = 0; k < ranks; k++) {
4209         for (j = 0; j < files; j++)
4210           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4211         if(gameInfo.holdingsWidth > 1) {
4212              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4213              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4214         }
4215       }
4216       CopyBoard(partnerBoard, board);
4217       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4218         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4219         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4220       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4221       if(toSqr = strchr(str, '-')) {
4222         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4223         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4224       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4225       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4226       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4227       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4228       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4229       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4230                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4231       DisplayMessage(partnerStatus, "");
4232         partnerBoardValid = TRUE;
4233       return;
4234     }
4235
4236     /* Modify behavior for initial board display on move listing
4237        of wild games.
4238        */
4239     switch (ics_getting_history) {
4240       case H_FALSE:
4241       case H_REQUESTED:
4242         break;
4243       case H_GOT_REQ_HEADER:
4244       case H_GOT_UNREQ_HEADER:
4245         /* This is the initial position of the current game */
4246         gamenum = ics_gamenum;
4247         moveNum = 0;            /* old ICS bug workaround */
4248         if (to_play == 'B') {
4249           startedFromSetupPosition = TRUE;
4250           blackPlaysFirst = TRUE;
4251           moveNum = 1;
4252           if (forwardMostMove == 0) forwardMostMove = 1;
4253           if (backwardMostMove == 0) backwardMostMove = 1;
4254           if (currentMove == 0) currentMove = 1;
4255         }
4256         newGameMode = gameMode;
4257         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4258         break;
4259       case H_GOT_UNWANTED_HEADER:
4260         /* This is an initial board that we don't want */
4261         return;
4262       case H_GETTING_MOVES:
4263         /* Should not happen */
4264         DisplayError(_("Error gathering move list: extra board"), 0);
4265         ics_getting_history = H_FALSE;
4266         return;
4267     }
4268
4269    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4270                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4271      /* [HGM] We seem to have switched variant unexpectedly
4272       * Try to guess new variant from board size
4273       */
4274           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4275           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4276           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4277           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4278           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4279           if(!weird) newVariant = VariantNormal;
4280           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4281           /* Get a move list just to see the header, which
4282              will tell us whether this is really bug or zh */
4283           if (ics_getting_history == H_FALSE) {
4284             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4285             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4286             SendToICS(str);
4287           }
4288     }
4289
4290     /* Take action if this is the first board of a new game, or of a
4291        different game than is currently being displayed.  */
4292     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4293         relation == RELATION_ISOLATED_BOARD) {
4294
4295         /* Forget the old game and get the history (if any) of the new one */
4296         if (gameMode != BeginningOfGame) {
4297           Reset(TRUE, TRUE);
4298         }
4299         newGame = TRUE;
4300         if (appData.autoRaiseBoard) BoardToTop();
4301         prevMove = -3;
4302         if (gamenum == -1) {
4303             newGameMode = IcsIdle;
4304         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4305                    appData.getMoveList && !reqFlag) {
4306             /* Need to get game history */
4307             ics_getting_history = H_REQUESTED;
4308             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4309             SendToICS(str);
4310         }
4311
4312         /* Initially flip the board to have black on the bottom if playing
4313            black or if the ICS flip flag is set, but let the user change
4314            it with the Flip View button. */
4315         flipView = appData.autoFlipView ?
4316           (newGameMode == IcsPlayingBlack) || ics_flip :
4317           appData.flipView;
4318
4319         /* Done with values from previous mode; copy in new ones */
4320         gameMode = newGameMode;
4321         ModeHighlight();
4322         ics_gamenum = gamenum;
4323         if (gamenum == gs_gamenum) {
4324             int klen = strlen(gs_kind);
4325             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4326             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4327             gameInfo.event = StrSave(str);
4328         } else {
4329             gameInfo.event = StrSave("ICS game");
4330         }
4331         gameInfo.site = StrSave(appData.icsHost);
4332         gameInfo.date = PGNDate();
4333         gameInfo.round = StrSave("-");
4334         gameInfo.white = StrSave(white);
4335         gameInfo.black = StrSave(black);
4336         timeControl = basetime * 60 * 1000;
4337         timeControl_2 = 0;
4338         timeIncrement = increment * 1000;
4339         movesPerSession = 0;
4340         gameInfo.timeControl = TimeControlTagValue();
4341         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4342   if (appData.debugMode) {
4343     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4344     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4345     setbuf(debugFP, NULL);
4346   }
4347
4348         gameInfo.outOfBook = NULL;
4349
4350         /* Do we have the ratings? */
4351         if (strcmp(player1Name, white) == 0 &&
4352             strcmp(player2Name, black) == 0) {
4353             if (appData.debugMode)
4354               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4355                       player1Rating, player2Rating);
4356             gameInfo.whiteRating = player1Rating;
4357             gameInfo.blackRating = player2Rating;
4358         } else if (strcmp(player2Name, white) == 0 &&
4359                    strcmp(player1Name, black) == 0) {
4360             if (appData.debugMode)
4361               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4362                       player2Rating, player1Rating);
4363             gameInfo.whiteRating = player2Rating;
4364             gameInfo.blackRating = player1Rating;
4365         }
4366         player1Name[0] = player2Name[0] = NULLCHAR;
4367
4368         /* Silence shouts if requested */
4369         if (appData.quietPlay &&
4370             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4371             SendToICS(ics_prefix);
4372             SendToICS("set shout 0\n");
4373         }
4374     }
4375
4376     /* Deal with midgame name changes */
4377     if (!newGame) {
4378         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4379             if (gameInfo.white) free(gameInfo.white);
4380             gameInfo.white = StrSave(white);
4381         }
4382         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4383             if (gameInfo.black) free(gameInfo.black);
4384             gameInfo.black = StrSave(black);
4385         }
4386     }
4387
4388     /* Throw away game result if anything actually changes in examine mode */
4389     if (gameMode == IcsExamining && !newGame) {
4390         gameInfo.result = GameUnfinished;
4391         if (gameInfo.resultDetails != NULL) {
4392             free(gameInfo.resultDetails);
4393             gameInfo.resultDetails = NULL;
4394         }
4395     }
4396
4397     /* In pausing && IcsExamining mode, we ignore boards coming
4398        in if they are in a different variation than we are. */
4399     if (pauseExamInvalid) return;
4400     if (pausing && gameMode == IcsExamining) {
4401         if (moveNum <= pauseExamForwardMostMove) {
4402             pauseExamInvalid = TRUE;
4403             forwardMostMove = pauseExamForwardMostMove;
4404             return;
4405         }
4406     }
4407
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4410   }
4411     /* Parse the board */
4412     for (k = 0; k < ranks; k++) {
4413       for (j = 0; j < files; j++)
4414         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4415       if(gameInfo.holdingsWidth > 1) {
4416            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4417            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4418       }
4419     }
4420     CopyBoard(boards[moveNum], board);
4421     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4422     if (moveNum == 0) {
4423         startedFromSetupPosition =
4424           !CompareBoards(board, initialPosition);
4425         if(startedFromSetupPosition)
4426             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4427     }
4428
4429     /* [HGM] Set castling rights. Take the outermost Rooks,
4430        to make it also work for FRC opening positions. Note that board12
4431        is really defective for later FRC positions, as it has no way to
4432        indicate which Rook can castle if they are on the same side of King.
4433        For the initial position we grant rights to the outermost Rooks,
4434        and remember thos rights, and we then copy them on positions
4435        later in an FRC game. This means WB might not recognize castlings with
4436        Rooks that have moved back to their original position as illegal,
4437        but in ICS mode that is not its job anyway.
4438     */
4439     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4440     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4441
4442         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4443             if(board[0][i] == WhiteRook) j = i;
4444         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4446             if(board[0][i] == WhiteRook) j = i;
4447         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4449             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4450         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4451         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4452             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4453         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454
4455         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4456         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4457             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4458         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4459             if(board[BOARD_HEIGHT-1][k] == bKing)
4460                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4461         if(gameInfo.variant == VariantTwoKings) {
4462             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4463             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4464             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4465         }
4466     } else { int r;
4467         r = boards[moveNum][CASTLING][0] = initialRights[0];
4468         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4469         r = boards[moveNum][CASTLING][1] = initialRights[1];
4470         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4471         r = boards[moveNum][CASTLING][3] = initialRights[3];
4472         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4473         r = boards[moveNum][CASTLING][4] = initialRights[4];
4474         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4475         /* wildcastle kludge: always assume King has rights */
4476         r = boards[moveNum][CASTLING][2] = initialRights[2];
4477         r = boards[moveNum][CASTLING][5] = initialRights[5];
4478     }
4479     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4480     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4481
4482
4483     if (ics_getting_history == H_GOT_REQ_HEADER ||
4484         ics_getting_history == H_GOT_UNREQ_HEADER) {
4485         /* This was an initial position from a move list, not
4486            the current position */
4487         return;
4488     }
4489
4490     /* Update currentMove and known move number limits */
4491     newMove = newGame || moveNum > forwardMostMove;
4492
4493     if (newGame) {
4494         forwardMostMove = backwardMostMove = currentMove = moveNum;
4495         if (gameMode == IcsExamining && moveNum == 0) {
4496           /* Workaround for ICS limitation: we are not told the wild
4497              type when starting to examine a game.  But if we ask for
4498              the move list, the move list header will tell us */
4499             ics_getting_history = H_REQUESTED;
4500             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4501             SendToICS(str);
4502         }
4503     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4504                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4505 #if ZIPPY
4506         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4507         /* [HGM] applied this also to an engine that is silently watching        */
4508         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4509             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4510             gameInfo.variant == currentlyInitializedVariant) {
4511           takeback = forwardMostMove - moveNum;
4512           for (i = 0; i < takeback; i++) {
4513             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4514             SendToProgram("undo\n", &first);
4515           }
4516         }
4517 #endif
4518
4519         forwardMostMove = moveNum;
4520         if (!pausing || currentMove > forwardMostMove)
4521           currentMove = forwardMostMove;
4522     } else {
4523         /* New part of history that is not contiguous with old part */
4524         if (pausing && gameMode == IcsExamining) {
4525             pauseExamInvalid = TRUE;
4526             forwardMostMove = pauseExamForwardMostMove;
4527             return;
4528         }
4529         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4530 #if ZIPPY
4531             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4532                 // [HGM] when we will receive the move list we now request, it will be
4533                 // fed to the engine from the first move on. So if the engine is not
4534                 // in the initial position now, bring it there.
4535                 InitChessProgram(&first, 0);
4536             }
4537 #endif
4538             ics_getting_history = H_REQUESTED;
4539             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4540             SendToICS(str);
4541         }
4542         forwardMostMove = backwardMostMove = currentMove = moveNum;
4543     }
4544
4545     /* Update the clocks */
4546     if (strchr(elapsed_time, '.')) {
4547       /* Time is in ms */
4548       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4549       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4550     } else {
4551       /* Time is in seconds */
4552       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4553       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4554     }
4555
4556
4557 #if ZIPPY
4558     if (appData.zippyPlay && newGame &&
4559         gameMode != IcsObserving && gameMode != IcsIdle &&
4560         gameMode != IcsExamining)
4561       ZippyFirstBoard(moveNum, basetime, increment);
4562 #endif
4563
4564     /* Put the move on the move list, first converting
4565        to canonical algebraic form. */
4566     if (moveNum > 0) {
4567   if (appData.debugMode) {
4568     if (appData.debugMode) { int f = forwardMostMove;
4569         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4570                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4571                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4572     }
4573     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4574     fprintf(debugFP, "moveNum = %d\n", moveNum);
4575     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4576     setbuf(debugFP, NULL);
4577   }
4578         if (moveNum <= backwardMostMove) {
4579             /* We don't know what the board looked like before
4580                this move.  Punt. */
4581           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4582             strcat(parseList[moveNum - 1], " ");
4583             strcat(parseList[moveNum - 1], elapsed_time);
4584             moveList[moveNum - 1][0] = NULLCHAR;
4585         } else if (strcmp(move_str, "none") == 0) {
4586             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4587             /* Again, we don't know what the board looked like;
4588                this is really the start of the game. */
4589             parseList[moveNum - 1][0] = NULLCHAR;
4590             moveList[moveNum - 1][0] = NULLCHAR;
4591             backwardMostMove = moveNum;
4592             startedFromSetupPosition = TRUE;
4593             fromX = fromY = toX = toY = -1;
4594         } else {
4595           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4596           //                 So we parse the long-algebraic move string in stead of the SAN move
4597           int valid; char buf[MSG_SIZ], *prom;
4598
4599           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4600                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4601           // str looks something like "Q/a1-a2"; kill the slash
4602           if(str[1] == '/')
4603             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4604           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4605           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4606                 strcat(buf, prom); // long move lacks promo specification!
4607           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4608                 if(appData.debugMode)
4609                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4610                 safeStrCpy(move_str, buf, MSG_SIZ);
4611           }
4612           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4613                                 &fromX, &fromY, &toX, &toY, &promoChar)
4614                || ParseOneMove(buf, moveNum - 1, &moveType,
4615                                 &fromX, &fromY, &toX, &toY, &promoChar);
4616           // end of long SAN patch
4617           if (valid) {
4618             (void) CoordsToAlgebraic(boards[moveNum - 1],
4619                                      PosFlags(moveNum - 1),
4620                                      fromY, fromX, toY, toX, promoChar,
4621                                      parseList[moveNum-1]);
4622             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4623               case MT_NONE:
4624               case MT_STALEMATE:
4625               default:
4626                 break;
4627               case MT_CHECK:
4628                 if(gameInfo.variant != VariantShogi)
4629                     strcat(parseList[moveNum - 1], "+");
4630                 break;
4631               case MT_CHECKMATE:
4632               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4633                 strcat(parseList[moveNum - 1], "#");
4634                 break;
4635             }
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             /* currentMoveString is set as a side-effect of ParseOneMove */
4639             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4640             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4641             strcat(moveList[moveNum - 1], "\n");
4642
4643             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4644                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4645               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4646                 ChessSquare old, new = boards[moveNum][k][j];
4647                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4648                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4649                   if(old == new) continue;
4650                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4651                   else if(new == WhiteWazir || new == BlackWazir) {
4652                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4653                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4654                       else boards[moveNum][k][j] = old; // preserve type of Gold
4655                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4656                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4657               }
4658           } else {
4659             /* Move from ICS was illegal!?  Punt. */
4660             if (appData.debugMode) {
4661               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4662               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4663             }
4664             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4665             strcat(parseList[moveNum - 1], " ");
4666             strcat(parseList[moveNum - 1], elapsed_time);
4667             moveList[moveNum - 1][0] = NULLCHAR;
4668             fromX = fromY = toX = toY = -1;
4669           }
4670         }
4671   if (appData.debugMode) {
4672     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4673     setbuf(debugFP, NULL);
4674   }
4675
4676 #if ZIPPY
4677         /* Send move to chess program (BEFORE animating it). */
4678         if (appData.zippyPlay && !newGame && newMove &&
4679            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4680
4681             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4682                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4683                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4684                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4685                             move_str);
4686                     DisplayError(str, 0);
4687                 } else {
4688                     if (first.sendTime) {
4689                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4690                     }
4691                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4692                     if (firstMove && !bookHit) {
4693                         firstMove = FALSE;
4694                         if (first.useColors) {
4695                           SendToProgram(gameMode == IcsPlayingWhite ?
4696                                         "white\ngo\n" :
4697                                         "black\ngo\n", &first);
4698                         } else {
4699                           SendToProgram("go\n", &first);
4700                         }
4701                         first.maybeThinking = TRUE;
4702                     }
4703                 }
4704             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4705               if (moveList[moveNum - 1][0] == NULLCHAR) {
4706                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4707                 DisplayError(str, 0);
4708               } else {
4709                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4710                 SendMoveToProgram(moveNum - 1, &first);
4711               }
4712             }
4713         }
4714 #endif
4715     }
4716
4717     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4718         /* If move comes from a remote source, animate it.  If it
4719            isn't remote, it will have already been animated. */
4720         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4721             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4722         }
4723         if (!pausing && appData.highlightLastMove) {
4724             SetHighlights(fromX, fromY, toX, toY);
4725         }
4726     }
4727
4728     /* Start the clocks */
4729     whiteFlag = blackFlag = FALSE;
4730     appData.clockMode = !(basetime == 0 && increment == 0);
4731     if (ticking == 0) {
4732       ics_clock_paused = TRUE;
4733       StopClocks();
4734     } else if (ticking == 1) {
4735       ics_clock_paused = FALSE;
4736     }
4737     if (gameMode == IcsIdle ||
4738         relation == RELATION_OBSERVING_STATIC ||
4739         relation == RELATION_EXAMINING ||
4740         ics_clock_paused)
4741       DisplayBothClocks();
4742     else
4743       StartClocks();
4744
4745     /* Display opponents and material strengths */
4746     if (gameInfo.variant != VariantBughouse &&
4747         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4748         if (tinyLayout || smallLayout) {
4749             if(gameInfo.variant == VariantNormal)
4750               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4751                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4752                     basetime, increment);
4753             else
4754               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4755                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4756                     basetime, increment, (int) gameInfo.variant);
4757         } else {
4758             if(gameInfo.variant == VariantNormal)
4759               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4760                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4761                     basetime, increment);
4762             else
4763               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4764                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4765                     basetime, increment, VariantName(gameInfo.variant));
4766         }
4767         DisplayTitle(str);
4768   if (appData.debugMode) {
4769     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4770   }
4771     }
4772
4773
4774     /* Display the board */
4775     if (!pausing && !appData.noGUI) {
4776
4777       if (appData.premove)
4778           if (!gotPremove ||
4779              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4780              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4781               ClearPremoveHighlights();
4782
4783       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4784         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4785       DrawPosition(j, boards[currentMove]);
4786
4787       DisplayMove(moveNum - 1);
4788       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4789             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4790               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4791         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4792       }
4793     }
4794
4795     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4796 #if ZIPPY
4797     if(bookHit) { // [HGM] book: simulate book reply
4798         static char bookMove[MSG_SIZ]; // a bit generous?
4799
4800         programStats.nodes = programStats.depth = programStats.time =
4801         programStats.score = programStats.got_only_move = 0;
4802         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4803
4804         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4805         strcat(bookMove, bookHit);
4806         HandleMachineMove(bookMove, &first);
4807     }
4808 #endif
4809 }
4810
4811 void
4812 GetMoveListEvent()
4813 {
4814     char buf[MSG_SIZ];
4815     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4816         ics_getting_history = H_REQUESTED;
4817         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4818         SendToICS(buf);
4819     }
4820 }
4821
4822 void
4823 AnalysisPeriodicEvent(force)
4824      int force;
4825 {
4826     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4827          && !force) || !appData.periodicUpdates)
4828       return;
4829
4830     /* Send . command to Crafty to collect stats */
4831     SendToProgram(".\n", &first);
4832
4833     /* Don't send another until we get a response (this makes
4834        us stop sending to old Crafty's which don't understand
4835        the "." command (sending illegal cmds resets node count & time,
4836        which looks bad)) */
4837     programStats.ok_to_send = 0;
4838 }
4839
4840 void ics_update_width(new_width)
4841         int new_width;
4842 {
4843         ics_printf("set width %d\n", new_width);
4844 }
4845
4846 void
4847 SendMoveToProgram(moveNum, cps)
4848      int moveNum;
4849      ChessProgramState *cps;
4850 {
4851     char buf[MSG_SIZ];
4852
4853     if (cps->useUsermove) {
4854       SendToProgram("usermove ", cps);
4855     }
4856     if (cps->useSAN) {
4857       char *space;
4858       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4859         int len = space - parseList[moveNum];
4860         memcpy(buf, parseList[moveNum], len);
4861         buf[len++] = '\n';
4862         buf[len] = NULLCHAR;
4863       } else {
4864         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4865       }
4866       SendToProgram(buf, cps);
4867     } else {
4868       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4869         AlphaRank(moveList[moveNum], 4);
4870         SendToProgram(moveList[moveNum], cps);
4871         AlphaRank(moveList[moveNum], 4); // and back
4872       } else
4873       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4874        * the engine. It would be nice to have a better way to identify castle
4875        * moves here. */
4876       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4877                                                                          && cps->useOOCastle) {
4878         int fromX = moveList[moveNum][0] - AAA;
4879         int fromY = moveList[moveNum][1] - ONE;
4880         int toX = moveList[moveNum][2] - AAA;
4881         int toY = moveList[moveNum][3] - ONE;
4882         if((boards[moveNum][fromY][fromX] == WhiteKing
4883             && boards[moveNum][toY][toX] == WhiteRook)
4884            || (boards[moveNum][fromY][fromX] == BlackKing
4885                && boards[moveNum][toY][toX] == BlackRook)) {
4886           if(toX > fromX) SendToProgram("O-O\n", cps);
4887           else SendToProgram("O-O-O\n", cps);
4888         }
4889         else SendToProgram(moveList[moveNum], cps);
4890       }
4891       else SendToProgram(moveList[moveNum], cps);
4892       /* End of additions by Tord */
4893     }
4894
4895     /* [HGM] setting up the opening has brought engine in force mode! */
4896     /*       Send 'go' if we are in a mode where machine should play. */
4897     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4898         (gameMode == TwoMachinesPlay   ||
4899 #if ZIPPY
4900          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4901 #endif
4902          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4903         SendToProgram("go\n", cps);
4904   if (appData.debugMode) {
4905     fprintf(debugFP, "(extra)\n");
4906   }
4907     }
4908     setboardSpoiledMachineBlack = 0;
4909 }
4910
4911 void
4912 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4913      ChessMove moveType;
4914      int fromX, fromY, toX, toY;
4915      char promoChar;
4916 {
4917     char user_move[MSG_SIZ];
4918
4919     switch (moveType) {
4920       default:
4921         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4922                 (int)moveType, fromX, fromY, toX, toY);
4923         DisplayError(user_move + strlen("say "), 0);
4924         break;
4925       case WhiteKingSideCastle:
4926       case BlackKingSideCastle:
4927       case WhiteQueenSideCastleWild:
4928       case BlackQueenSideCastleWild:
4929       /* PUSH Fabien */
4930       case WhiteHSideCastleFR:
4931       case BlackHSideCastleFR:
4932       /* POP Fabien */
4933         snprintf(user_move, MSG_SIZ, "o-o\n");
4934         break;
4935       case WhiteQueenSideCastle:
4936       case BlackQueenSideCastle:
4937       case WhiteKingSideCastleWild:
4938       case BlackKingSideCastleWild:
4939       /* PUSH Fabien */
4940       case WhiteASideCastleFR:
4941       case BlackASideCastleFR:
4942       /* POP Fabien */
4943         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4944         break;
4945       case WhiteNonPromotion:
4946       case BlackNonPromotion:
4947         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4948         break;
4949       case WhitePromotion:
4950       case BlackPromotion:
4951         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4952           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4953                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4954                 PieceToChar(WhiteFerz));
4955         else if(gameInfo.variant == VariantGreat)
4956           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4957                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4958                 PieceToChar(WhiteMan));
4959         else
4960           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4961                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4962                 promoChar);
4963         break;
4964       case WhiteDrop:
4965       case BlackDrop:
4966       drop:
4967         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4968                  ToUpper(PieceToChar((ChessSquare) fromX)),
4969                  AAA + toX, ONE + toY);
4970         break;
4971       case IllegalMove:  /* could be a variant we don't quite understand */
4972         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4973       case NormalMove:
4974       case WhiteCapturesEnPassant:
4975       case BlackCapturesEnPassant:
4976         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4977                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4978         break;
4979     }
4980     SendToICS(user_move);
4981     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4982         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4983 }
4984
4985 void
4986 UploadGameEvent()
4987 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4988     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4989     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4990     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4991         DisplayError("You cannot do this while you are playing or observing", 0);
4992         return;
4993     }
4994     if(gameMode != IcsExamining) { // is this ever not the case?
4995         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4996
4997         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4998           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4999         } else { // on FICS we must first go to general examine mode
5000           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5001         }
5002         if(gameInfo.variant != VariantNormal) {
5003             // try figure out wild number, as xboard names are not always valid on ICS
5004             for(i=1; i<=36; i++) {
5005               snprintf(buf, MSG_SIZ, "wild/%d", i);
5006                 if(StringToVariant(buf) == gameInfo.variant) break;
5007             }
5008             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5009             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5010             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5011         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5012         SendToICS(ics_prefix);
5013         SendToICS(buf);
5014         if(startedFromSetupPosition || backwardMostMove != 0) {
5015           fen = PositionToFEN(backwardMostMove, NULL);
5016           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5017             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5018             SendToICS(buf);
5019           } else { // FICS: everything has to set by separate bsetup commands
5020             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5021             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5022             SendToICS(buf);
5023             if(!WhiteOnMove(backwardMostMove)) {
5024                 SendToICS("bsetup tomove black\n");
5025             }
5026             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5027             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5028             SendToICS(buf);
5029             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5030             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5031             SendToICS(buf);
5032             i = boards[backwardMostMove][EP_STATUS];
5033             if(i >= 0) { // set e.p.
5034               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5035                 SendToICS(buf);
5036             }
5037             bsetup++;
5038           }
5039         }
5040       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5041             SendToICS("bsetup done\n"); // switch to normal examining.
5042     }
5043     for(i = backwardMostMove; i<last; i++) {
5044         char buf[20];
5045         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5046         SendToICS(buf);
5047     }
5048     SendToICS(ics_prefix);
5049     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5050 }
5051
5052 void
5053 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5054      int rf, ff, rt, ft;
5055      char promoChar;
5056      char move[7];
5057 {
5058     if (rf == DROP_RANK) {
5059       sprintf(move, "%c@%c%c\n",
5060                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5061     } else {
5062         if (promoChar == 'x' || promoChar == NULLCHAR) {
5063           sprintf(move, "%c%c%c%c\n",
5064                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5065         } else {
5066             sprintf(move, "%c%c%c%c%c\n",
5067                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5068         }
5069     }
5070 }
5071
5072 void
5073 ProcessICSInitScript(f)
5074      FILE *f;
5075 {
5076     char buf[MSG_SIZ];
5077
5078     while (fgets(buf, MSG_SIZ, f)) {
5079         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5080     }
5081
5082     fclose(f);
5083 }
5084
5085
5086 static int lastX, lastY, selectFlag, dragging;
5087
5088 void
5089 Sweep(int step)
5090 {
5091     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5092     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5093     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5094     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5095     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5096     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5097     do {
5098         promoSweep -= step;
5099         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5100         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5101         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5102         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5103         if(!step) step = 1;
5104     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5105             appData.testLegality && (promoSweep == king ||
5106             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5107     ChangeDragPiece(promoSweep);
5108 }
5109
5110 int PromoScroll(int x, int y)
5111 {
5112   int step = 0;
5113
5114   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5115   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5116   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5117   if(!step) return FALSE;
5118   lastX = x; lastY = y;
5119   if((promoSweep < BlackPawn) == flipView) step = -step;
5120   if(step > 0) selectFlag = 1;
5121   if(!selectFlag) Sweep(step);
5122   return FALSE;
5123 }
5124
5125 void
5126 NextPiece(int step)
5127 {
5128     ChessSquare piece = boards[currentMove][toY][toX];
5129     do {
5130         pieceSweep -= step;
5131         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5132         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5133         if(!step) step = -1;
5134     } while(PieceToChar(pieceSweep) == '.');
5135     boards[currentMove][toY][toX] = pieceSweep;
5136     DrawPosition(FALSE, boards[currentMove]);
5137     boards[currentMove][toY][toX] = piece;
5138 }
5139 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5140 void
5141 AlphaRank(char *move, int n)
5142 {
5143 //    char *p = move, c; int x, y;
5144
5145     if (appData.debugMode) {
5146         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5147     }
5148
5149     if(move[1]=='*' &&
5150        move[2]>='0' && move[2]<='9' &&
5151        move[3]>='a' && move[3]<='x'    ) {
5152         move[1] = '@';
5153         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5154         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5155     } else
5156     if(move[0]>='0' && move[0]<='9' &&
5157        move[1]>='a' && move[1]<='x' &&
5158        move[2]>='0' && move[2]<='9' &&
5159        move[3]>='a' && move[3]<='x'    ) {
5160         /* input move, Shogi -> normal */
5161         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5162         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5163         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5164         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5165     } else
5166     if(move[1]=='@' &&
5167        move[3]>='0' && move[3]<='9' &&
5168        move[2]>='a' && move[2]<='x'    ) {
5169         move[1] = '*';
5170         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5171         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5172     } else
5173     if(
5174        move[0]>='a' && move[0]<='x' &&
5175        move[3]>='0' && move[3]<='9' &&
5176        move[2]>='a' && move[2]<='x'    ) {
5177          /* output move, normal -> Shogi */
5178         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5179         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5180         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5182         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5183     }
5184     if (appData.debugMode) {
5185         fprintf(debugFP, "   out = '%s'\n", move);
5186     }
5187 }
5188
5189 char yy_textstr[8000];
5190
5191 /* Parser for moves from gnuchess, ICS, or user typein box */
5192 Boolean
5193 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5194      char *move;
5195      int moveNum;
5196      ChessMove *moveType;
5197      int *fromX, *fromY, *toX, *toY;
5198      char *promoChar;
5199 {
5200     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5201
5202     switch (*moveType) {
5203       case WhitePromotion:
5204       case BlackPromotion:
5205       case WhiteNonPromotion:
5206       case BlackNonPromotion:
5207       case NormalMove:
5208       case WhiteCapturesEnPassant:
5209       case BlackCapturesEnPassant:
5210       case WhiteKingSideCastle:
5211       case WhiteQueenSideCastle:
5212       case BlackKingSideCastle:
5213       case BlackQueenSideCastle:
5214       case WhiteKingSideCastleWild:
5215       case WhiteQueenSideCastleWild:
5216       case BlackKingSideCastleWild:
5217       case BlackQueenSideCastleWild:
5218       /* Code added by Tord: */
5219       case WhiteHSideCastleFR:
5220       case WhiteASideCastleFR:
5221       case BlackHSideCastleFR:
5222       case BlackASideCastleFR:
5223       /* End of code added by Tord */
5224       case IllegalMove:         /* bug or odd chess variant */
5225         *fromX = currentMoveString[0] - AAA;
5226         *fromY = currentMoveString[1] - ONE;
5227         *toX = currentMoveString[2] - AAA;
5228         *toY = currentMoveString[3] - ONE;
5229         *promoChar = currentMoveString[4];
5230         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5231             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5232     if (appData.debugMode) {
5233         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5234     }
5235             *fromX = *fromY = *toX = *toY = 0;
5236             return FALSE;
5237         }
5238         if (appData.testLegality) {
5239           return (*moveType != IllegalMove);
5240         } else {
5241           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5242                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5243         }
5244
5245       case WhiteDrop:
5246       case BlackDrop:
5247         *fromX = *moveType == WhiteDrop ?
5248           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5249           (int) CharToPiece(ToLower(currentMoveString[0]));
5250         *fromY = DROP_RANK;
5251         *toX = currentMoveString[2] - AAA;
5252         *toY = currentMoveString[3] - ONE;
5253         *promoChar = NULLCHAR;
5254         return TRUE;
5255
5256       case AmbiguousMove:
5257       case ImpossibleMove:
5258       case EndOfFile:
5259       case ElapsedTime:
5260       case Comment:
5261       case PGNTag:
5262       case NAG:
5263       case WhiteWins:
5264       case BlackWins:
5265       case GameIsDrawn:
5266       default:
5267     if (appData.debugMode) {
5268         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5269     }
5270         /* bug? */
5271         *fromX = *fromY = *toX = *toY = 0;
5272         *promoChar = NULLCHAR;
5273         return FALSE;
5274     }
5275 }
5276
5277 Boolean pushed = FALSE;
5278
5279 void
5280 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5281 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5282   int fromX, fromY, toX, toY; char promoChar;
5283   ChessMove moveType;
5284   Boolean valid;
5285   int nr = 0;
5286
5287   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5288     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5289     pushed = TRUE;
5290   }
5291   endPV = forwardMostMove;
5292   do {
5293     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5294     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5295     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5296 if(appData.debugMode){
5297 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5298 }
5299     if(!valid && nr == 0 &&
5300        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5301         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5302         // Hande case where played move is different from leading PV move
5303         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5304         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5305         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5306         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5307           endPV += 2; // if position different, keep this
5308           moveList[endPV-1][0] = fromX + AAA;
5309           moveList[endPV-1][1] = fromY + ONE;
5310           moveList[endPV-1][2] = toX + AAA;
5311           moveList[endPV-1][3] = toY + ONE;
5312           parseList[endPV-1][0] = NULLCHAR;
5313           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5314         }
5315       }
5316     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5317     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5318     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5319     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5320         valid++; // allow comments in PV
5321         continue;
5322     }
5323     nr++;
5324     if(endPV+1 > framePtr) break; // no space, truncate
5325     if(!valid) break;
5326     endPV++;
5327     CopyBoard(boards[endPV], boards[endPV-1]);
5328     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5329     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5330     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5331     CoordsToAlgebraic(boards[endPV - 1],
5332                              PosFlags(endPV - 1),
5333                              fromY, fromX, toY, toX, promoChar,
5334                              parseList[endPV - 1]);
5335   } while(valid);
5336   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5337   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5338   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5339                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5340   DrawPosition(TRUE, boards[currentMove]);
5341 }
5342
5343 int
5344 MultiPV(ChessProgramState *cps)
5345 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5346         int i;
5347         for(i=0; i<cps->nrOptions; i++)
5348             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5349                 return i;
5350         return -1;
5351 }
5352
5353 Boolean
5354 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5355 {
5356         int startPV, multi, lineStart, origIndex = index;
5357         char *p, buf2[MSG_SIZ];
5358
5359         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5360         lastX = x; lastY = y;
5361         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5362         lineStart = startPV = index;
5363         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5364         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5365         index = startPV;
5366         do{ while(buf[index] && buf[index] != '\n') index++;
5367         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5368         buf[index] = 0;
5369         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5370                 int n = first.option[multi].value;
5371                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5372                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5373                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5374                 first.option[multi].value = n;
5375                 *start = *end = 0;
5376                 return FALSE;
5377         }
5378         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5379         *start = startPV; *end = index-1;
5380         return TRUE;
5381 }
5382
5383 Boolean
5384 LoadPV(int x, int y)
5385 { // called on right mouse click to load PV
5386   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5387   lastX = x; lastY = y;
5388   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5389   return TRUE;
5390 }
5391
5392 void
5393 UnLoadPV()
5394 {
5395   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5396   if(endPV < 0) return;
5397   endPV = -1;
5398   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5399         Boolean saveAnimate = appData.animate;
5400         if(pushed) {
5401             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5402                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5403             } else storedGames--; // abandon shelved tail of original game
5404         }
5405         pushed = FALSE;
5406         forwardMostMove = currentMove;
5407         currentMove = oldFMM;
5408         appData.animate = FALSE;
5409         ToNrEvent(forwardMostMove);
5410         appData.animate = saveAnimate;
5411   }
5412   currentMove = forwardMostMove;
5413   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5414   ClearPremoveHighlights();
5415   DrawPosition(TRUE, boards[currentMove]);
5416 }
5417
5418 void
5419 MovePV(int x, int y, int h)
5420 { // step through PV based on mouse coordinates (called on mouse move)
5421   int margin = h>>3, step = 0;
5422
5423   // we must somehow check if right button is still down (might be released off board!)
5424   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5425   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5426   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5427   if(!step) return;
5428   lastX = x; lastY = y;
5429
5430   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5431   if(endPV < 0) return;
5432   if(y < margin) step = 1; else
5433   if(y > h - margin) step = -1;
5434   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5435   currentMove += step;
5436   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5437   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5438                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5439   DrawPosition(FALSE, boards[currentMove]);
5440 }
5441
5442
5443 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5444 // All positions will have equal probability, but the current method will not provide a unique
5445 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5446 #define DARK 1
5447 #define LITE 2
5448 #define ANY 3
5449
5450 int squaresLeft[4];
5451 int piecesLeft[(int)BlackPawn];
5452 int seed, nrOfShuffles;
5453
5454 void GetPositionNumber()
5455 {       // sets global variable seed
5456         int i;
5457
5458         seed = appData.defaultFrcPosition;
5459         if(seed < 0) { // randomize based on time for negative FRC position numbers
5460                 for(i=0; i<50; i++) seed += random();
5461                 seed = random() ^ random() >> 8 ^ random() << 8;
5462                 if(seed<0) seed = -seed;
5463         }
5464 }
5465
5466 int put(Board board, int pieceType, int rank, int n, int shade)
5467 // put the piece on the (n-1)-th empty squares of the given shade
5468 {
5469         int i;
5470
5471         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5472                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5473                         board[rank][i] = (ChessSquare) pieceType;
5474                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5475                         squaresLeft[ANY]--;
5476                         piecesLeft[pieceType]--;
5477                         return i;
5478                 }
5479         }
5480         return -1;
5481 }
5482
5483
5484 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5485 // calculate where the next piece goes, (any empty square), and put it there
5486 {
5487         int i;
5488
5489         i = seed % squaresLeft[shade];
5490         nrOfShuffles *= squaresLeft[shade];
5491         seed /= squaresLeft[shade];
5492         put(board, pieceType, rank, i, shade);
5493 }
5494
5495 void AddTwoPieces(Board board, int pieceType, int rank)
5496 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5497 {
5498         int i, n=squaresLeft[ANY], j=n-1, k;
5499
5500         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5501         i = seed % k;  // pick one
5502         nrOfShuffles *= k;
5503         seed /= k;
5504         while(i >= j) i -= j--;
5505         j = n - 1 - j; i += j;
5506         put(board, pieceType, rank, j, ANY);
5507         put(board, pieceType, rank, i, ANY);
5508 }
5509
5510 void SetUpShuffle(Board board, int number)
5511 {
5512         int i, p, first=1;
5513
5514         GetPositionNumber(); nrOfShuffles = 1;
5515
5516         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5517         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5518         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5519
5520         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5521
5522         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5523             p = (int) board[0][i];
5524             if(p < (int) BlackPawn) piecesLeft[p] ++;
5525             board[0][i] = EmptySquare;
5526         }
5527
5528         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5529             // shuffles restricted to allow normal castling put KRR first
5530             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5531                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5532             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5533                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5534             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5535                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5536             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5537                 put(board, WhiteRook, 0, 0, ANY);
5538             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5539         }
5540
5541         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5542             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5543             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5544                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5545                 while(piecesLeft[p] >= 2) {
5546                     AddOnePiece(board, p, 0, LITE);
5547                     AddOnePiece(board, p, 0, DARK);
5548                 }
5549                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5550             }
5551
5552         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5553             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5554             // but we leave King and Rooks for last, to possibly obey FRC restriction
5555             if(p == (int)WhiteRook) continue;
5556             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5557             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5558         }
5559
5560         // now everything is placed, except perhaps King (Unicorn) and Rooks
5561
5562         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5563             // Last King gets castling rights
5564             while(piecesLeft[(int)WhiteUnicorn]) {
5565                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5566                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5567             }
5568
5569             while(piecesLeft[(int)WhiteKing]) {
5570                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5571                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5572             }
5573
5574
5575         } else {
5576             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5577             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5578         }
5579
5580         // Only Rooks can be left; simply place them all
5581         while(piecesLeft[(int)WhiteRook]) {
5582                 i = put(board, WhiteRook, 0, 0, ANY);
5583                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5584                         if(first) {
5585                                 first=0;
5586                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5587                         }
5588                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5589                 }
5590         }
5591         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5592             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5593         }
5594
5595         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5596 }
5597
5598 int SetCharTable( char *table, const char * map )
5599 /* [HGM] moved here from winboard.c because of its general usefulness */
5600 /*       Basically a safe strcpy that uses the last character as King */
5601 {
5602     int result = FALSE; int NrPieces;
5603
5604     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5605                     && NrPieces >= 12 && !(NrPieces&1)) {
5606         int i; /* [HGM] Accept even length from 12 to 34 */
5607
5608         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5609         for( i=0; i<NrPieces/2-1; i++ ) {
5610             table[i] = map[i];
5611             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5612         }
5613         table[(int) WhiteKing]  = map[NrPieces/2-1];
5614         table[(int) BlackKing]  = map[NrPieces-1];
5615
5616         result = TRUE;
5617     }
5618
5619     return result;
5620 }
5621
5622 void Prelude(Board board)
5623 {       // [HGM] superchess: random selection of exo-pieces
5624         int i, j, k; ChessSquare p;
5625         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5626
5627         GetPositionNumber(); // use FRC position number
5628
5629         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5630             SetCharTable(pieceToChar, appData.pieceToCharTable);
5631             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5632                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5633         }
5634
5635         j = seed%4;                 seed /= 4;
5636         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5637         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5638         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5639         j = seed%3 + (seed%3 >= j); seed /= 3;
5640         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5641         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5642         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5643         j = seed%3;                 seed /= 3;
5644         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5645         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5646         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5647         j = seed%2 + (seed%2 >= j); seed /= 2;
5648         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5649         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5650         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5651         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5652         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5653         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5654         put(board, exoPieces[0],    0, 0, ANY);
5655         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5656 }
5657
5658 void
5659 InitPosition(redraw)
5660      int redraw;
5661 {
5662     ChessSquare (* pieces)[BOARD_FILES];
5663     int i, j, pawnRow, overrule,
5664     oldx = gameInfo.boardWidth,
5665     oldy = gameInfo.boardHeight,
5666     oldh = gameInfo.holdingsWidth;
5667     static int oldv;
5668
5669     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5670
5671     /* [AS] Initialize pv info list [HGM] and game status */
5672     {
5673         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5674             pvInfoList[i].depth = 0;
5675             boards[i][EP_STATUS] = EP_NONE;
5676             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5677         }
5678
5679         initialRulePlies = 0; /* 50-move counter start */
5680
5681         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5682         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5683     }
5684
5685
5686     /* [HGM] logic here is completely changed. In stead of full positions */
5687     /* the initialized data only consist of the two backranks. The switch */
5688     /* selects which one we will use, which is than copied to the Board   */
5689     /* initialPosition, which for the rest is initialized by Pawns and    */
5690     /* empty squares. This initial position is then copied to boards[0],  */
5691     /* possibly after shuffling, so that it remains available.            */
5692
5693     gameInfo.holdingsWidth = 0; /* default board sizes */
5694     gameInfo.boardWidth    = 8;
5695     gameInfo.boardHeight   = 8;
5696     gameInfo.holdingsSize  = 0;
5697     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5698     for(i=0; i<BOARD_FILES-2; i++)
5699       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5700     initialPosition[EP_STATUS] = EP_NONE;
5701     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5702     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5703          SetCharTable(pieceNickName, appData.pieceNickNames);
5704     else SetCharTable(pieceNickName, "............");
5705     pieces = FIDEArray;
5706
5707     switch (gameInfo.variant) {
5708     case VariantFischeRandom:
5709       shuffleOpenings = TRUE;
5710     default:
5711       break;
5712     case VariantShatranj:
5713       pieces = ShatranjArray;
5714       nrCastlingRights = 0;
5715       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5716       break;
5717     case VariantMakruk:
5718       pieces = makrukArray;
5719       nrCastlingRights = 0;
5720       startedFromSetupPosition = TRUE;
5721       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5722       break;
5723     case VariantTwoKings:
5724       pieces = twoKingsArray;
5725       break;
5726     case VariantCapaRandom:
5727       shuffleOpenings = TRUE;
5728     case VariantCapablanca:
5729       pieces = CapablancaArray;
5730       gameInfo.boardWidth = 10;
5731       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5732       break;
5733     case VariantGothic:
5734       pieces = GothicArray;
5735       gameInfo.boardWidth = 10;
5736       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5737       break;
5738     case VariantSChess:
5739       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5740       gameInfo.holdingsSize = 7;
5741       break;
5742     case VariantJanus:
5743       pieces = JanusArray;
5744       gameInfo.boardWidth = 10;
5745       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5746       nrCastlingRights = 6;
5747         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5748         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5749         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5750         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5751         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5752         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5753       break;
5754     case VariantFalcon:
5755       pieces = FalconArray;
5756       gameInfo.boardWidth = 10;
5757       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5758       break;
5759     case VariantXiangqi:
5760       pieces = XiangqiArray;
5761       gameInfo.boardWidth  = 9;
5762       gameInfo.boardHeight = 10;
5763       nrCastlingRights = 0;
5764       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5765       break;
5766     case VariantShogi:
5767       pieces = ShogiArray;
5768       gameInfo.boardWidth  = 9;
5769       gameInfo.boardHeight = 9;
5770       gameInfo.holdingsSize = 7;
5771       nrCastlingRights = 0;
5772       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5773       break;
5774     case VariantCourier:
5775       pieces = CourierArray;
5776       gameInfo.boardWidth  = 12;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5779       break;
5780     case VariantKnightmate:
5781       pieces = KnightmateArray;
5782       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5783       break;
5784     case VariantSpartan:
5785       pieces = SpartanArray;
5786       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5787       break;
5788     case VariantFairy:
5789       pieces = fairyArray;
5790       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5791       break;
5792     case VariantGreat:
5793       pieces = GreatArray;
5794       gameInfo.boardWidth = 10;
5795       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5796       gameInfo.holdingsSize = 8;
5797       break;
5798     case VariantSuper:
5799       pieces = FIDEArray;
5800       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5801       gameInfo.holdingsSize = 8;
5802       startedFromSetupPosition = TRUE;
5803       break;
5804     case VariantCrazyhouse:
5805     case VariantBughouse:
5806       pieces = FIDEArray;
5807       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5808       gameInfo.holdingsSize = 5;
5809       break;
5810     case VariantWildCastle:
5811       pieces = FIDEArray;
5812       /* !!?shuffle with kings guaranteed to be on d or e file */
5813       shuffleOpenings = 1;
5814       break;
5815     case VariantNoCastle:
5816       pieces = FIDEArray;
5817       nrCastlingRights = 0;
5818       /* !!?unconstrained back-rank shuffle */
5819       shuffleOpenings = 1;
5820       break;
5821     }
5822
5823     overrule = 0;
5824     if(appData.NrFiles >= 0) {
5825         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5826         gameInfo.boardWidth = appData.NrFiles;
5827     }
5828     if(appData.NrRanks >= 0) {
5829         gameInfo.boardHeight = appData.NrRanks;
5830     }
5831     if(appData.holdingsSize >= 0) {
5832         i = appData.holdingsSize;
5833         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5834         gameInfo.holdingsSize = i;
5835     }
5836     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5837     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5838         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5839
5840     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5841     if(pawnRow < 1) pawnRow = 1;
5842     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5843
5844     /* User pieceToChar list overrules defaults */
5845     if(appData.pieceToCharTable != NULL)
5846         SetCharTable(pieceToChar, appData.pieceToCharTable);
5847
5848     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5849
5850         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5851             s = (ChessSquare) 0; /* account holding counts in guard band */
5852         for( i=0; i<BOARD_HEIGHT; i++ )
5853             initialPosition[i][j] = s;
5854
5855         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5856         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5857         initialPosition[pawnRow][j] = WhitePawn;
5858         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5859         if(gameInfo.variant == VariantXiangqi) {
5860             if(j&1) {
5861                 initialPosition[pawnRow][j] =
5862                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5863                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5864                    initialPosition[2][j] = WhiteCannon;
5865                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5866                 }
5867             }
5868         }
5869         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5870     }
5871     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5872
5873             j=BOARD_LEFT+1;
5874             initialPosition[1][j] = WhiteBishop;
5875             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5876             j=BOARD_RGHT-2;
5877             initialPosition[1][j] = WhiteRook;
5878             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5879     }
5880
5881     if( nrCastlingRights == -1) {
5882         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5883         /*       This sets default castling rights from none to normal corners   */
5884         /* Variants with other castling rights must set them themselves above    */
5885         nrCastlingRights = 6;
5886
5887         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5888         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5889         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5890         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5891         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5892         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5893      }
5894
5895      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5896      if(gameInfo.variant == VariantGreat) { // promotion commoners
5897         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5898         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5899         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5900         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5901      }
5902      if( gameInfo.variant == VariantSChess ) {
5903       initialPosition[1][0] = BlackMarshall;
5904       initialPosition[2][0] = BlackAngel;
5905       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5906       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5907       initialPosition[1][1] = initialPosition[2][1] = 
5908       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5909      }
5910   if (appData.debugMode) {
5911     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5912   }
5913     if(shuffleOpenings) {
5914         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5915         startedFromSetupPosition = TRUE;
5916     }
5917     if(startedFromPositionFile) {
5918       /* [HGM] loadPos: use PositionFile for every new game */
5919       CopyBoard(initialPosition, filePosition);
5920       for(i=0; i<nrCastlingRights; i++)
5921           initialRights[i] = filePosition[CASTLING][i];
5922       startedFromSetupPosition = TRUE;
5923     }
5924
5925     CopyBoard(boards[0], initialPosition);
5926
5927     if(oldx != gameInfo.boardWidth ||
5928        oldy != gameInfo.boardHeight ||
5929        oldv != gameInfo.variant ||
5930        oldh != gameInfo.holdingsWidth
5931                                          )
5932             InitDrawingSizes(-2 ,0);
5933
5934     oldv = gameInfo.variant;
5935     if (redraw)
5936       DrawPosition(TRUE, boards[currentMove]);
5937 }
5938
5939 void
5940 SendBoard(cps, moveNum)
5941      ChessProgramState *cps;
5942      int moveNum;
5943 {
5944     char message[MSG_SIZ];
5945
5946     if (cps->useSetboard) {
5947       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5948       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5949       SendToProgram(message, cps);
5950       free(fen);
5951
5952     } else {
5953       ChessSquare *bp;
5954       int i, j;
5955       /* Kludge to set black to move, avoiding the troublesome and now
5956        * deprecated "black" command.
5957        */
5958       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5959         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5960
5961       SendToProgram("edit\n", cps);
5962       SendToProgram("#\n", cps);
5963       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5964         bp = &boards[moveNum][i][BOARD_LEFT];
5965         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5966           if ((int) *bp < (int) BlackPawn) {
5967             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5968                     AAA + j, ONE + i);
5969             if(message[0] == '+' || message[0] == '~') {
5970               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5971                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5972                         AAA + j, ONE + i);
5973             }
5974             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5975                 message[1] = BOARD_RGHT   - 1 - j + '1';
5976                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5977             }
5978             SendToProgram(message, cps);
5979           }
5980         }
5981       }
5982
5983       SendToProgram("c\n", cps);
5984       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5985         bp = &boards[moveNum][i][BOARD_LEFT];
5986         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5987           if (((int) *bp != (int) EmptySquare)
5988               && ((int) *bp >= (int) BlackPawn)) {
5989             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5990                     AAA + j, ONE + i);
5991             if(message[0] == '+' || message[0] == '~') {
5992               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5993                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5994                         AAA + j, ONE + i);
5995             }
5996             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5997                 message[1] = BOARD_RGHT   - 1 - j + '1';
5998                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5999             }
6000             SendToProgram(message, cps);
6001           }
6002         }
6003       }
6004
6005       SendToProgram(".\n", cps);
6006     }
6007     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6008 }
6009
6010 ChessSquare
6011 DefaultPromoChoice(int white)
6012 {
6013     ChessSquare result;
6014     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6015         result = WhiteFerz; // no choice
6016     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6017         result= WhiteKing; // in Suicide Q is the last thing we want
6018     else if(gameInfo.variant == VariantSpartan)
6019         result = white ? WhiteQueen : WhiteAngel;
6020     else result = WhiteQueen;
6021     if(!white) result = WHITE_TO_BLACK result;
6022     return result;
6023 }
6024
6025 static int autoQueen; // [HGM] oneclick
6026
6027 int
6028 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6029 {
6030     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6031     /* [HGM] add Shogi promotions */
6032     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6033     ChessSquare piece;
6034     ChessMove moveType;
6035     Boolean premove;
6036
6037     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6038     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6039
6040     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6041       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6042         return FALSE;
6043
6044     piece = boards[currentMove][fromY][fromX];
6045     if(gameInfo.variant == VariantShogi) {
6046         promotionZoneSize = BOARD_HEIGHT/3;
6047         highestPromotingPiece = (int)WhiteFerz;
6048     } else if(gameInfo.variant == VariantMakruk) {
6049         promotionZoneSize = 3;
6050     }
6051
6052     // Treat Lance as Pawn when it is not representing Amazon
6053     if(gameInfo.variant != VariantSuper) {
6054         if(piece == WhiteLance) piece = WhitePawn; else
6055         if(piece == BlackLance) piece = BlackPawn;
6056     }
6057
6058     // next weed out all moves that do not touch the promotion zone at all
6059     if((int)piece >= BlackPawn) {
6060         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6061              return FALSE;
6062         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6063     } else {
6064         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6065            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6066     }
6067
6068     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6069
6070     // weed out mandatory Shogi promotions
6071     if(gameInfo.variant == VariantShogi) {
6072         if(piece >= BlackPawn) {
6073             if(toY == 0 && piece == BlackPawn ||
6074                toY == 0 && piece == BlackQueen ||
6075                toY <= 1 && piece == BlackKnight) {
6076                 *promoChoice = '+';
6077                 return FALSE;
6078             }
6079         } else {
6080             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6081                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6082                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6083                 *promoChoice = '+';
6084                 return FALSE;
6085             }
6086         }
6087     }
6088
6089     // weed out obviously illegal Pawn moves
6090     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6091         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6092         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6093         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6094         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6095         // note we are not allowed to test for valid (non-)capture, due to premove
6096     }
6097
6098     // we either have a choice what to promote to, or (in Shogi) whether to promote
6099     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6100         *promoChoice = PieceToChar(BlackFerz);  // no choice
6101         return FALSE;
6102     }
6103     // no sense asking what we must promote to if it is going to explode...
6104     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6105         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6106         return FALSE;
6107     }
6108     // give caller the default choice even if we will not make it
6109     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6110     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6111     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6112                            && gameInfo.variant != VariantShogi
6113                            && gameInfo.variant != VariantSuper) return FALSE;
6114     if(autoQueen) return FALSE; // predetermined
6115
6116     // suppress promotion popup on illegal moves that are not premoves
6117     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6118               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6119     if(appData.testLegality && !premove) {
6120         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6121                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6122         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6123             return FALSE;
6124     }
6125
6126     return TRUE;
6127 }
6128
6129 int
6130 InPalace(row, column)
6131      int row, column;
6132 {   /* [HGM] for Xiangqi */
6133     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6134          column < (BOARD_WIDTH + 4)/2 &&
6135          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6136     return FALSE;
6137 }
6138
6139 int
6140 PieceForSquare (x, y)
6141      int x;
6142      int y;
6143 {
6144   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6145      return -1;
6146   else
6147      return boards[currentMove][y][x];
6148 }
6149
6150 int
6151 OKToStartUserMove(x, y)
6152      int x, y;
6153 {
6154     ChessSquare from_piece;
6155     int white_piece;
6156
6157     if (matchMode) return FALSE;
6158     if (gameMode == EditPosition) return TRUE;
6159
6160     if (x >= 0 && y >= 0)
6161       from_piece = boards[currentMove][y][x];
6162     else
6163       from_piece = EmptySquare;
6164
6165     if (from_piece == EmptySquare) return FALSE;
6166
6167     white_piece = (int)from_piece >= (int)WhitePawn &&
6168       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6169
6170     switch (gameMode) {
6171       case PlayFromGameFile:
6172       case AnalyzeFile:
6173       case TwoMachinesPlay:
6174       case EndOfGame:
6175         return FALSE;
6176
6177       case IcsObserving:
6178       case IcsIdle:
6179         return FALSE;
6180
6181       case MachinePlaysWhite:
6182       case IcsPlayingBlack:
6183         if (appData.zippyPlay) return FALSE;
6184         if (white_piece) {
6185             DisplayMoveError(_("You are playing Black"));
6186             return FALSE;
6187         }
6188         break;
6189
6190       case MachinePlaysBlack:
6191       case IcsPlayingWhite:
6192         if (appData.zippyPlay) return FALSE;
6193         if (!white_piece) {
6194             DisplayMoveError(_("You are playing White"));
6195             return FALSE;
6196         }
6197         break;
6198
6199       case EditGame:
6200         if (!white_piece && WhiteOnMove(currentMove)) {
6201             DisplayMoveError(_("It is White's turn"));
6202             return FALSE;
6203         }
6204         if (white_piece && !WhiteOnMove(currentMove)) {
6205             DisplayMoveError(_("It is Black's turn"));
6206             return FALSE;
6207         }
6208         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6209             /* Editing correspondence game history */
6210             /* Could disallow this or prompt for confirmation */
6211             cmailOldMove = -1;
6212         }
6213         break;
6214
6215       case BeginningOfGame:
6216         if (appData.icsActive) return FALSE;
6217         if (!appData.noChessProgram) {
6218             if (!white_piece) {
6219                 DisplayMoveError(_("You are playing White"));
6220                 return FALSE;
6221             }
6222         }
6223         break;
6224
6225       case Training:
6226         if (!white_piece && WhiteOnMove(currentMove)) {
6227             DisplayMoveError(_("It is White's turn"));
6228             return FALSE;
6229         }
6230         if (white_piece && !WhiteOnMove(currentMove)) {
6231             DisplayMoveError(_("It is Black's turn"));
6232             return FALSE;
6233         }
6234         break;
6235
6236       default:
6237       case IcsExamining:
6238         break;
6239     }
6240     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6241         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6242         && gameMode != AnalyzeFile && gameMode != Training) {
6243         DisplayMoveError(_("Displayed position is not current"));
6244         return FALSE;
6245     }
6246     return TRUE;
6247 }
6248
6249 Boolean
6250 OnlyMove(int *x, int *y, Boolean captures) {
6251     DisambiguateClosure cl;
6252     if (appData.zippyPlay) return FALSE;
6253     switch(gameMode) {
6254       case MachinePlaysBlack:
6255       case IcsPlayingWhite:
6256       case BeginningOfGame:
6257         if(!WhiteOnMove(currentMove)) return FALSE;
6258         break;
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if(WhiteOnMove(currentMove)) return FALSE;
6262         break;
6263       case EditGame:
6264         break;
6265       default:
6266         return FALSE;
6267     }
6268     cl.pieceIn = EmptySquare;
6269     cl.rfIn = *y;
6270     cl.ffIn = *x;
6271     cl.rtIn = -1;
6272     cl.ftIn = -1;
6273     cl.promoCharIn = NULLCHAR;
6274     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6275     if( cl.kind == NormalMove ||
6276         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6277         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6278         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6279       fromX = cl.ff;
6280       fromY = cl.rf;
6281       *x = cl.ft;
6282       *y = cl.rt;
6283       return TRUE;
6284     }
6285     if(cl.kind != ImpossibleMove) return FALSE;
6286     cl.pieceIn = EmptySquare;
6287     cl.rfIn = -1;
6288     cl.ffIn = -1;
6289     cl.rtIn = *y;
6290     cl.ftIn = *x;
6291     cl.promoCharIn = NULLCHAR;
6292     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6293     if( cl.kind == NormalMove ||
6294         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6295         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6296         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6297       fromX = cl.ff;
6298       fromY = cl.rf;
6299       *x = cl.ft;
6300       *y = cl.rt;
6301       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6302       return TRUE;
6303     }
6304     return FALSE;
6305 }
6306
6307 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6308 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6309 int lastLoadGameUseList = FALSE;
6310 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6311 ChessMove lastLoadGameStart = EndOfFile;
6312
6313 void
6314 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6315      int fromX, fromY, toX, toY;
6316      int promoChar;
6317 {
6318     ChessMove moveType;
6319     ChessSquare pdown, pup;
6320
6321     /* Check if the user is playing in turn.  This is complicated because we
6322        let the user "pick up" a piece before it is his turn.  So the piece he
6323        tried to pick up may have been captured by the time he puts it down!
6324        Therefore we use the color the user is supposed to be playing in this
6325        test, not the color of the piece that is currently on the starting
6326        square---except in EditGame mode, where the user is playing both
6327        sides; fortunately there the capture race can't happen.  (It can
6328        now happen in IcsExamining mode, but that's just too bad.  The user
6329        will get a somewhat confusing message in that case.)
6330        */
6331
6332     switch (gameMode) {
6333       case PlayFromGameFile:
6334       case AnalyzeFile:
6335       case TwoMachinesPlay:
6336       case EndOfGame:
6337       case IcsObserving:
6338       case IcsIdle:
6339         /* We switched into a game mode where moves are not accepted,
6340            perhaps while the mouse button was down. */
6341         return;
6342
6343       case MachinePlaysWhite:
6344         /* User is moving for Black */
6345         if (WhiteOnMove(currentMove)) {
6346             DisplayMoveError(_("It is White's turn"));
6347             return;
6348         }
6349         break;
6350
6351       case MachinePlaysBlack:
6352         /* User is moving for White */
6353         if (!WhiteOnMove(currentMove)) {
6354             DisplayMoveError(_("It is Black's turn"));
6355             return;
6356         }
6357         break;
6358
6359       case EditGame:
6360       case IcsExamining:
6361       case BeginningOfGame:
6362       case AnalyzeMode:
6363       case Training:
6364         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6365         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6366             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6367             /* User is moving for Black */
6368             if (WhiteOnMove(currentMove)) {
6369                 DisplayMoveError(_("It is White's turn"));
6370                 return;
6371             }
6372         } else {
6373             /* User is moving for White */
6374             if (!WhiteOnMove(currentMove)) {
6375                 DisplayMoveError(_("It is Black's turn"));
6376                 return;
6377             }
6378         }
6379         break;
6380
6381       case IcsPlayingBlack:
6382         /* User is moving for Black */
6383         if (WhiteOnMove(currentMove)) {
6384             if (!appData.premove) {
6385                 DisplayMoveError(_("It is White's turn"));
6386             } else if (toX >= 0 && toY >= 0) {
6387                 premoveToX = toX;
6388                 premoveToY = toY;
6389                 premoveFromX = fromX;
6390                 premoveFromY = fromY;
6391                 premovePromoChar = promoChar;
6392                 gotPremove = 1;
6393                 if (appData.debugMode)
6394                     fprintf(debugFP, "Got premove: fromX %d,"
6395                             "fromY %d, toX %d, toY %d\n",
6396                             fromX, fromY, toX, toY);
6397             }
6398             return;
6399         }
6400         break;
6401
6402       case IcsPlayingWhite:
6403         /* User is moving for White */
6404         if (!WhiteOnMove(currentMove)) {
6405             if (!appData.premove) {
6406                 DisplayMoveError(_("It is Black's turn"));
6407             } else if (toX >= 0 && toY >= 0) {
6408                 premoveToX = toX;
6409                 premoveToY = toY;
6410                 premoveFromX = fromX;
6411                 premoveFromY = fromY;
6412                 premovePromoChar = promoChar;
6413                 gotPremove = 1;
6414                 if (appData.debugMode)
6415                     fprintf(debugFP, "Got premove: fromX %d,"
6416                             "fromY %d, toX %d, toY %d\n",
6417                             fromX, fromY, toX, toY);
6418             }
6419             return;
6420         }
6421         break;
6422
6423       default:
6424         break;
6425
6426       case EditPosition:
6427         /* EditPosition, empty square, or different color piece;
6428            click-click move is possible */
6429         if (toX == -2 || toY == -2) {
6430             boards[0][fromY][fromX] = EmptySquare;
6431             DrawPosition(FALSE, boards[currentMove]);
6432             return;
6433         } else if (toX >= 0 && toY >= 0) {
6434             boards[0][toY][toX] = boards[0][fromY][fromX];
6435             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6436                 if(boards[0][fromY][0] != EmptySquare) {
6437                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6438                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6439                 }
6440             } else
6441             if(fromX == BOARD_RGHT+1) {
6442                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6443                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6444                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6445                 }
6446             } else
6447             boards[0][fromY][fromX] = EmptySquare;
6448             DrawPosition(FALSE, boards[currentMove]);
6449             return;
6450         }
6451         return;
6452     }
6453
6454     if(toX < 0 || toY < 0) return;
6455     pdown = boards[currentMove][fromY][fromX];
6456     pup = boards[currentMove][toY][toX];
6457
6458     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6459     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6460          if( pup != EmptySquare ) return;
6461          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6462            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6463                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6464            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6465            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6466            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6467            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6468          fromY = DROP_RANK;
6469     }
6470
6471     /* [HGM] always test for legality, to get promotion info */
6472     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6473                                          fromY, fromX, toY, toX, promoChar);
6474     /* [HGM] but possibly ignore an IllegalMove result */
6475     if (appData.testLegality) {
6476         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6477             DisplayMoveError(_("Illegal move"));
6478             return;
6479         }
6480     }
6481
6482     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6483 }
6484
6485 /* Common tail of UserMoveEvent and DropMenuEvent */
6486 int
6487 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6488      ChessMove moveType;
6489      int fromX, fromY, toX, toY;
6490      /*char*/int promoChar;
6491 {
6492     char *bookHit = 0;
6493
6494     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6495         // [HGM] superchess: suppress promotions to non-available piece
6496         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6497         if(WhiteOnMove(currentMove)) {
6498             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6499         } else {
6500             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6501         }
6502     }
6503
6504     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6505        move type in caller when we know the move is a legal promotion */
6506     if(moveType == NormalMove && promoChar)
6507         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6508
6509     /* [HGM] <popupFix> The following if has been moved here from
6510        UserMoveEvent(). Because it seemed to belong here (why not allow
6511        piece drops in training games?), and because it can only be
6512        performed after it is known to what we promote. */
6513     if (gameMode == Training) {
6514       /* compare the move played on the board to the next move in the
6515        * game. If they match, display the move and the opponent's response.
6516        * If they don't match, display an error message.
6517        */
6518       int saveAnimate;
6519       Board testBoard;
6520       CopyBoard(testBoard, boards[currentMove]);
6521       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6522
6523       if (CompareBoards(testBoard, boards[currentMove+1])) {
6524         ForwardInner(currentMove+1);
6525
6526         /* Autoplay the opponent's response.
6527          * if appData.animate was TRUE when Training mode was entered,
6528          * the response will be animated.
6529          */
6530         saveAnimate = appData.animate;
6531         appData.animate = animateTraining;
6532         ForwardInner(currentMove+1);
6533         appData.animate = saveAnimate;
6534
6535         /* check for the end of the game */
6536         if (currentMove >= forwardMostMove) {
6537           gameMode = PlayFromGameFile;
6538           ModeHighlight();
6539           SetTrainingModeOff();
6540           DisplayInformation(_("End of game"));
6541         }
6542       } else {
6543         DisplayError(_("Incorrect move"), 0);
6544       }
6545       return 1;
6546     }
6547
6548   /* Ok, now we know that the move is good, so we can kill
6549      the previous line in Analysis Mode */
6550   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6551                                 && currentMove < forwardMostMove) {
6552     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6553     else forwardMostMove = currentMove;
6554   }
6555
6556   /* If we need the chess program but it's dead, restart it */
6557   ResurrectChessProgram();
6558
6559   /* A user move restarts a paused game*/
6560   if (pausing)
6561     PauseEvent();
6562
6563   thinkOutput[0] = NULLCHAR;
6564
6565   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6566
6567   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6568     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6569     return 1;
6570   }
6571
6572   if (gameMode == BeginningOfGame) {
6573     if (appData.noChessProgram) {
6574       gameMode = EditGame;
6575       SetGameInfo();
6576     } else {
6577       char buf[MSG_SIZ];
6578       gameMode = MachinePlaysBlack;
6579       StartClocks();
6580       SetGameInfo();
6581       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6582       DisplayTitle(buf);
6583       if (first.sendName) {
6584         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6585         SendToProgram(buf, &first);
6586       }
6587       StartClocks();
6588     }
6589     ModeHighlight();
6590   }
6591
6592   /* Relay move to ICS or chess engine */
6593   if (appData.icsActive) {
6594     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6595         gameMode == IcsExamining) {
6596       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6597         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6598         SendToICS("draw ");
6599         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6600       }
6601       // also send plain move, in case ICS does not understand atomic claims
6602       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6603       ics_user_moved = 1;
6604     }
6605   } else {
6606     if (first.sendTime && (gameMode == BeginningOfGame ||
6607                            gameMode == MachinePlaysWhite ||
6608                            gameMode == MachinePlaysBlack)) {
6609       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6610     }
6611     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6612          // [HGM] book: if program might be playing, let it use book
6613         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6614         first.maybeThinking = TRUE;
6615     } else SendMoveToProgram(forwardMostMove-1, &first);
6616     if (currentMove == cmailOldMove + 1) {
6617       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6618     }
6619   }
6620
6621   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6622
6623   switch (gameMode) {
6624   case EditGame:
6625     if(appData.testLegality)
6626     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6627     case MT_NONE:
6628     case MT_CHECK:
6629       break;
6630     case MT_CHECKMATE:
6631     case MT_STAINMATE:
6632       if (WhiteOnMove(currentMove)) {
6633         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6634       } else {
6635         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6636       }
6637       break;
6638     case MT_STALEMATE:
6639       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6640       break;
6641     }
6642     break;
6643
6644   case MachinePlaysBlack:
6645   case MachinePlaysWhite:
6646     /* disable certain menu options while machine is thinking */
6647     SetMachineThinkingEnables();
6648     break;
6649
6650   default:
6651     break;
6652   }
6653
6654   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6655   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6656
6657   if(bookHit) { // [HGM] book: simulate book reply
6658         static char bookMove[MSG_SIZ]; // a bit generous?
6659
6660         programStats.nodes = programStats.depth = programStats.time =
6661         programStats.score = programStats.got_only_move = 0;
6662         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6663
6664         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6665         strcat(bookMove, bookHit);
6666         HandleMachineMove(bookMove, &first);
6667   }
6668   return 1;
6669 }
6670
6671 void
6672 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6673      Board board;
6674      int flags;
6675      ChessMove kind;
6676      int rf, ff, rt, ft;
6677      VOIDSTAR closure;
6678 {
6679     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6680     Markers *m = (Markers *) closure;
6681     if(rf == fromY && ff == fromX)
6682         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6683                          || kind == WhiteCapturesEnPassant
6684                          || kind == BlackCapturesEnPassant);
6685     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6686 }
6687
6688 void
6689 MarkTargetSquares(int clear)
6690 {
6691   int x, y;
6692   if(!appData.markers || !appData.highlightDragging ||
6693      !appData.testLegality || gameMode == EditPosition) return;
6694   if(clear) {
6695     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6696   } else {
6697     int capt = 0;
6698     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6699     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6700       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6701       if(capt)
6702       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6703     }
6704   }
6705   DrawPosition(TRUE, NULL);
6706 }
6707
6708 int
6709 Explode(Board board, int fromX, int fromY, int toX, int toY)
6710 {
6711     if(gameInfo.variant == VariantAtomic &&
6712        (board[toY][toX] != EmptySquare ||                     // capture?
6713         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6714                          board[fromY][fromX] == BlackPawn   )
6715       )) {
6716         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6717         return TRUE;
6718     }
6719     return FALSE;
6720 }
6721
6722 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6723
6724 int CanPromote(ChessSquare piece, int y)
6725 {
6726         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6727         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6728         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6729            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6730            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6731                                                   gameInfo.variant == VariantMakruk) return FALSE;
6732         return (piece == BlackPawn && y == 1 ||
6733                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6734                 piece == BlackLance && y == 1 ||
6735                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6736 }
6737
6738 void LeftClick(ClickType clickType, int xPix, int yPix)
6739 {
6740     int x, y;
6741     Boolean saveAnimate;
6742     static int second = 0, promotionChoice = 0, clearFlag = 0;
6743     char promoChoice = NULLCHAR;
6744     ChessSquare piece;
6745
6746     if(appData.seekGraph && appData.icsActive && loggedOn &&
6747         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6748         SeekGraphClick(clickType, xPix, yPix, 0);
6749         return;
6750     }
6751
6752     if (clickType == Press) ErrorPopDown();
6753     MarkTargetSquares(1);
6754
6755     x = EventToSquare(xPix, BOARD_WIDTH);
6756     y = EventToSquare(yPix, BOARD_HEIGHT);
6757     if (!flipView && y >= 0) {
6758         y = BOARD_HEIGHT - 1 - y;
6759     }
6760     if (flipView && x >= 0) {
6761         x = BOARD_WIDTH - 1 - x;
6762     }
6763
6764     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6765         defaultPromoChoice = promoSweep;
6766         promoSweep = EmptySquare;   // terminate sweep
6767         promoDefaultAltered = TRUE;
6768         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6769     }
6770
6771     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6772         if(clickType == Release) return; // ignore upclick of click-click destination
6773         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6774         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6775         if(gameInfo.holdingsWidth &&
6776                 (WhiteOnMove(currentMove)
6777                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6778                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6779             // click in right holdings, for determining promotion piece
6780             ChessSquare p = boards[currentMove][y][x];
6781             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6782             if(p != EmptySquare) {
6783                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6784                 fromX = fromY = -1;
6785                 return;
6786             }
6787         }
6788         DrawPosition(FALSE, boards[currentMove]);
6789         return;
6790     }
6791
6792     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6793     if(clickType == Press
6794             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6795               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6796               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6797         return;
6798
6799     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6800         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6801
6802     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6803         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6804                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6805         defaultPromoChoice = DefaultPromoChoice(side);
6806     }
6807
6808     autoQueen = appData.alwaysPromoteToQueen;
6809
6810     if (fromX == -1) {
6811       int originalY = y;
6812       gatingPiece = EmptySquare;
6813       if (clickType != Press) {
6814         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6815             DragPieceEnd(xPix, yPix); dragging = 0;
6816             DrawPosition(FALSE, NULL);
6817         }
6818         return;
6819       }
6820       fromX = x; fromY = y;
6821       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6822          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6823          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6824             /* First square */
6825             if (OKToStartUserMove(fromX, fromY)) {
6826                 second = 0;
6827                 MarkTargetSquares(0);
6828                 DragPieceBegin(xPix, yPix); dragging = 1;
6829                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6830                     promoSweep = defaultPromoChoice;
6831                     selectFlag = 0; lastX = xPix; lastY = yPix;
6832                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6833                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6834                 }
6835                 if (appData.highlightDragging) {
6836                     SetHighlights(fromX, fromY, -1, -1);
6837                 }
6838             } else fromX = fromY = -1;
6839             return;
6840         }
6841     }
6842
6843     /* fromX != -1 */
6844     if (clickType == Press && gameMode != EditPosition) {
6845         ChessSquare fromP;
6846         ChessSquare toP;
6847         int frc;
6848
6849         // ignore off-board to clicks
6850         if(y < 0 || x < 0) return;
6851
6852         /* Check if clicking again on the same color piece */
6853         fromP = boards[currentMove][fromY][fromX];
6854         toP = boards[currentMove][y][x];
6855         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6856         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6857              WhitePawn <= toP && toP <= WhiteKing &&
6858              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6859              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6860             (BlackPawn <= fromP && fromP <= BlackKing &&
6861              BlackPawn <= toP && toP <= BlackKing &&
6862              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6863              !(fromP == BlackKing && toP == BlackRook && frc))) {
6864             /* Clicked again on same color piece -- changed his mind */
6865             second = (x == fromX && y == fromY);
6866             promoDefaultAltered = FALSE;
6867            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6868             if (appData.highlightDragging) {
6869                 SetHighlights(x, y, -1, -1);
6870             } else {
6871                 ClearHighlights();
6872             }
6873             if (OKToStartUserMove(x, y)) {
6874                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6875                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6876                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6877                  gatingPiece = boards[currentMove][fromY][fromX];
6878                 else gatingPiece = EmptySquare;
6879                 fromX = x;
6880                 fromY = y; dragging = 1;
6881                 MarkTargetSquares(0);
6882                 DragPieceBegin(xPix, yPix);
6883                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6884                     promoSweep = defaultPromoChoice;
6885                     selectFlag = 0; lastX = xPix; lastY = yPix;
6886                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6887                 }
6888             }
6889            }
6890            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6891            second = FALSE; 
6892         }
6893         // ignore clicks on holdings
6894         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6895     }
6896
6897     if (clickType == Release && x == fromX && y == fromY) {
6898         DragPieceEnd(xPix, yPix); dragging = 0;
6899         if(clearFlag) {
6900             // a deferred attempt to click-click move an empty square on top of a piece
6901             boards[currentMove][y][x] = EmptySquare;
6902             ClearHighlights();
6903             DrawPosition(FALSE, boards[currentMove]);
6904             fromX = fromY = -1; clearFlag = 0;
6905             return;
6906         }
6907         if (appData.animateDragging) {
6908             /* Undo animation damage if any */
6909             DrawPosition(FALSE, NULL);
6910         }
6911         if (second) {
6912             /* Second up/down in same square; just abort move */
6913             second = 0;
6914             fromX = fromY = -1;
6915             gatingPiece = EmptySquare;
6916             ClearHighlights();
6917             gotPremove = 0;
6918             ClearPremoveHighlights();
6919         } else {
6920             /* First upclick in same square; start click-click mode */
6921             SetHighlights(x, y, -1, -1);
6922         }
6923         return;
6924     }
6925
6926     clearFlag = 0;
6927
6928     /* we now have a different from- and (possibly off-board) to-square */
6929     /* Completed move */
6930     toX = x;
6931     toY = y;
6932     saveAnimate = appData.animate;
6933     if (clickType == Press) {
6934         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6935             // must be Edit Position mode with empty-square selected
6936             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6937             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6938             return;
6939         }
6940         /* Finish clickclick move */
6941         if (appData.animate || appData.highlightLastMove) {
6942             SetHighlights(fromX, fromY, toX, toY);
6943         } else {
6944             ClearHighlights();
6945         }
6946     } else {
6947         /* Finish drag move */
6948         if (appData.highlightLastMove) {
6949             SetHighlights(fromX, fromY, toX, toY);
6950         } else {
6951             ClearHighlights();
6952         }
6953         DragPieceEnd(xPix, yPix); dragging = 0;
6954         /* Don't animate move and drag both */
6955         appData.animate = FALSE;
6956     }
6957
6958     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6959     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6960         ChessSquare piece = boards[currentMove][fromY][fromX];
6961         if(gameMode == EditPosition && piece != EmptySquare &&
6962            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6963             int n;
6964
6965             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6966                 n = PieceToNumber(piece - (int)BlackPawn);
6967                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6968                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6969                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6970             } else
6971             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6972                 n = PieceToNumber(piece);
6973                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6974                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6975                 boards[currentMove][n][BOARD_WIDTH-2]++;
6976             }
6977             boards[currentMove][fromY][fromX] = EmptySquare;
6978         }
6979         ClearHighlights();
6980         fromX = fromY = -1;
6981         DrawPosition(TRUE, boards[currentMove]);
6982         return;
6983     }
6984
6985     // off-board moves should not be highlighted
6986     if(x < 0 || y < 0) ClearHighlights();
6987
6988     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6989
6990     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6991         SetHighlights(fromX, fromY, toX, toY);
6992         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6993             // [HGM] super: promotion to captured piece selected from holdings
6994             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6995             promotionChoice = TRUE;
6996             // kludge follows to temporarily execute move on display, without promoting yet
6997             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6998             boards[currentMove][toY][toX] = p;
6999             DrawPosition(FALSE, boards[currentMove]);
7000             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7001             boards[currentMove][toY][toX] = q;
7002             DisplayMessage("Click in holdings to choose piece", "");
7003             return;
7004         }
7005         PromotionPopUp();
7006     } else {
7007         int oldMove = currentMove;
7008         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7009         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7010         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7011         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7012            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7013             DrawPosition(TRUE, boards[currentMove]);
7014         fromX = fromY = -1;
7015     }
7016     appData.animate = saveAnimate;
7017     if (appData.animate || appData.animateDragging) {
7018         /* Undo animation damage if needed */
7019         DrawPosition(FALSE, NULL);
7020     }
7021 }
7022
7023 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7024 {   // front-end-free part taken out of PieceMenuPopup
7025     int whichMenu; int xSqr, ySqr;
7026
7027     if(seekGraphUp) { // [HGM] seekgraph
7028         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7029         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7030         return -2;
7031     }
7032
7033     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7034          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7035         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7036         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7037         if(action == Press)   {
7038             originalFlip = flipView;
7039             flipView = !flipView; // temporarily flip board to see game from partners perspective
7040             DrawPosition(TRUE, partnerBoard);
7041             DisplayMessage(partnerStatus, "");
7042             partnerUp = TRUE;
7043         } else if(action == Release) {
7044             flipView = originalFlip;
7045             DrawPosition(TRUE, boards[currentMove]);
7046             partnerUp = FALSE;
7047         }
7048         return -2;
7049     }
7050
7051     xSqr = EventToSquare(x, BOARD_WIDTH);
7052     ySqr = EventToSquare(y, BOARD_HEIGHT);
7053     if (action == Release) {
7054         if(pieceSweep != EmptySquare) {
7055             EditPositionMenuEvent(pieceSweep, toX, toY);
7056             pieceSweep = EmptySquare;
7057         } else UnLoadPV(); // [HGM] pv
7058     }
7059     if (action != Press) return -2; // return code to be ignored
7060     switch (gameMode) {
7061       case IcsExamining:
7062         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7063       case EditPosition:
7064         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7065         if (xSqr < 0 || ySqr < 0) return -1;
7066         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7067         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7068         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7069         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7070         NextPiece(0);
7071         return -2;\r
7072       case IcsObserving:
7073         if(!appData.icsEngineAnalyze) return -1;
7074       case IcsPlayingWhite:
7075       case IcsPlayingBlack:
7076         if(!appData.zippyPlay) goto noZip;
7077       case AnalyzeMode:
7078       case AnalyzeFile:
7079       case MachinePlaysWhite:
7080       case MachinePlaysBlack:
7081       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7082         if (!appData.dropMenu) {
7083           LoadPV(x, y);
7084           return 2; // flag front-end to grab mouse events
7085         }
7086         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7087            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7088       case EditGame:
7089       noZip:
7090         if (xSqr < 0 || ySqr < 0) return -1;
7091         if (!appData.dropMenu || appData.testLegality &&
7092             gameInfo.variant != VariantBughouse &&
7093             gameInfo.variant != VariantCrazyhouse) return -1;
7094         whichMenu = 1; // drop menu
7095         break;
7096       default:
7097         return -1;
7098     }
7099
7100     if (((*fromX = xSqr) < 0) ||
7101         ((*fromY = ySqr) < 0)) {
7102         *fromX = *fromY = -1;
7103         return -1;
7104     }
7105     if (flipView)
7106       *fromX = BOARD_WIDTH - 1 - *fromX;
7107     else
7108       *fromY = BOARD_HEIGHT - 1 - *fromY;
7109
7110     return whichMenu;
7111 }
7112
7113 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7114 {
7115 //    char * hint = lastHint;
7116     FrontEndProgramStats stats;
7117
7118     stats.which = cps == &first ? 0 : 1;
7119     stats.depth = cpstats->depth;
7120     stats.nodes = cpstats->nodes;
7121     stats.score = cpstats->score;
7122     stats.time = cpstats->time;
7123     stats.pv = cpstats->movelist;
7124     stats.hint = lastHint;
7125     stats.an_move_index = 0;
7126     stats.an_move_count = 0;
7127
7128     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7129         stats.hint = cpstats->move_name;
7130         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7131         stats.an_move_count = cpstats->nr_moves;
7132     }
7133
7134     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7135
7136     SetProgramStats( &stats );
7137 }
7138
7139 void
7140 ClearEngineOutputPane(int which)
7141 {
7142     static FrontEndProgramStats dummyStats;
7143     dummyStats.which = which;
7144     dummyStats.pv = "#";
7145     SetProgramStats( &dummyStats );
7146 }
7147
7148 #define MAXPLAYERS 500
7149
7150 char *
7151 TourneyStandings(int display)
7152 {
7153     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7154     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7155     char result, *p, *names[MAXPLAYERS];
7156
7157     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7158         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7159     names[0] = p = strdup(appData.participants);
7160     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7161
7162     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7163
7164     while(result = appData.results[nr]) {
7165         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7166         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7167         wScore = bScore = 0;
7168         switch(result) {
7169           case '+': wScore = 2; break;
7170           case '-': bScore = 2; break;
7171           case '=': wScore = bScore = 1; break;
7172           case ' ':
7173           case '*': return strdup("busy"); // tourney not finished
7174         }
7175         score[w] += wScore;
7176         score[b] += bScore;
7177         games[w]++;
7178         games[b]++;
7179         nr++;
7180     }
7181     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7182     for(w=0; w<nPlayers; w++) {
7183         bScore = -1;
7184         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7185         ranking[w] = b; points[w] = bScore; score[b] = -2;
7186     }
7187     p = malloc(nPlayers*34+1);
7188     for(w=0; w<nPlayers && w<display; w++)
7189         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7190     free(names[0]);
7191     return p;
7192 }
7193
7194 void
7195 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7196 {       // count all piece types
7197         int p, f, r;
7198         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7199         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7200         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7201                 p = board[r][f];
7202                 pCnt[p]++;
7203                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7204                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7205                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7206                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7207                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7208                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7209         }
7210 }
7211
7212 int
7213 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7214 {
7215         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7216         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7217
7218         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7219         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7220         if(myPawns == 2 && nMine == 3) // KPP
7221             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7222         if(myPawns == 1 && nMine == 2) // KP
7223             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7224         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7225             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7226         if(myPawns) return FALSE;
7227         if(pCnt[WhiteRook+side])
7228             return pCnt[BlackRook-side] ||
7229                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7230                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7231                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7232         if(pCnt[WhiteCannon+side]) {
7233             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7234             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7235         }
7236         if(pCnt[WhiteKnight+side])
7237             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7238         return FALSE;
7239 }
7240
7241 int
7242 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7243 {
7244         VariantClass v = gameInfo.variant;
7245
7246         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7247         if(v == VariantShatranj) return TRUE; // always winnable through baring
7248         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7249         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7250
7251         if(v == VariantXiangqi) {
7252                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7253
7254                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7255                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7256                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7257                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7258                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7259                 if(stale) // we have at least one last-rank P plus perhaps C
7260                     return majors // KPKX
7261                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7262                 else // KCA*E*
7263                     return pCnt[WhiteFerz+side] // KCAK
7264                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7265                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7266                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7267
7268         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7269                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7270
7271                 if(nMine == 1) return FALSE; // bare King
7272                 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
7273                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7274                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7275                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7276                 if(pCnt[WhiteKnight+side])
7277                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7278                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7279                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7280                 if(nBishops)
7281                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7282                 if(pCnt[WhiteAlfil+side])
7283                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7284                 if(pCnt[WhiteWazir+side])
7285                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7286         }
7287
7288         return TRUE;
7289 }
7290
7291 int
7292 Adjudicate(ChessProgramState *cps)
7293 {       // [HGM] some adjudications useful with buggy engines
7294         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7295         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7296         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7297         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7298         int k, count = 0; static int bare = 1;
7299         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7300         Boolean canAdjudicate = !appData.icsActive;
7301
7302         // most tests only when we understand the game, i.e. legality-checking on
7303             if( appData.testLegality )
7304             {   /* [HGM] Some more adjudications for obstinate engines */
7305                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7306                 static int moveCount = 6;
7307                 ChessMove result;
7308                 char *reason = NULL;
7309
7310                 /* Count what is on board. */
7311                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7312
7313                 /* Some material-based adjudications that have to be made before stalemate test */
7314                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7315                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7316                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7317                      if(canAdjudicate && appData.checkMates) {
7318                          if(engineOpponent)
7319                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7320                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7321                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7322                          return 1;
7323                      }
7324                 }
7325
7326                 /* Bare King in Shatranj (loses) or Losers (wins) */
7327                 if( nrW == 1 || nrB == 1) {
7328                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7329                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7330                      if(canAdjudicate && appData.checkMates) {
7331                          if(engineOpponent)
7332                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7333                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7334                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7335                          return 1;
7336                      }
7337                   } else
7338                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7339                   {    /* bare King */
7340                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7341                         if(canAdjudicate && appData.checkMates) {
7342                             /* but only adjudicate if adjudication enabled */
7343                             if(engineOpponent)
7344                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7345                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7346                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7347                             return 1;
7348                         }
7349                   }
7350                 } else bare = 1;
7351
7352
7353             // don't wait for engine to announce game end if we can judge ourselves
7354             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7355               case MT_CHECK:
7356                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7357                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7358                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7359                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7360                             checkCnt++;
7361                         if(checkCnt >= 2) {
7362                             reason = "Xboard adjudication: 3rd check";
7363                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7364                             break;
7365                         }
7366                     }
7367                 }
7368               case MT_NONE:
7369               default:
7370                 break;
7371               case MT_STALEMATE:
7372               case MT_STAINMATE:
7373                 reason = "Xboard adjudication: Stalemate";
7374                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7375                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7376                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7377                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7378                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7379                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7380                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7381                                                                         EP_CHECKMATE : EP_WINS);
7382                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7383                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7384                 }
7385                 break;
7386               case MT_CHECKMATE:
7387                 reason = "Xboard adjudication: Checkmate";
7388                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7389                 break;
7390             }
7391
7392                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7393                     case EP_STALEMATE:
7394                         result = GameIsDrawn; break;
7395                     case EP_CHECKMATE:
7396                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7397                     case EP_WINS:
7398                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7399                     default:
7400                         result = EndOfFile;
7401                 }
7402                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7403                     if(engineOpponent)
7404                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7405                     GameEnds( result, reason, GE_XBOARD );
7406                     return 1;
7407                 }
7408
7409                 /* Next absolutely insufficient mating material. */
7410                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7411                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7412                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7413
7414                      /* always flag draws, for judging claims */
7415                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7416
7417                      if(canAdjudicate && appData.materialDraws) {
7418                          /* but only adjudicate them if adjudication enabled */
7419                          if(engineOpponent) {
7420                            SendToProgram("force\n", engineOpponent); // suppress reply
7421                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7422                          }
7423                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7424                          return 1;
7425                      }
7426                 }
7427
7428                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7429                 if(gameInfo.variant == VariantXiangqi ?
7430                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7431                  : nrW + nrB == 4 &&
7432                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7433                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7434                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7435                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7436                    ) ) {
7437                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7438                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7439                           if(engineOpponent) {
7440                             SendToProgram("force\n", engineOpponent); // suppress reply
7441                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7442                           }
7443                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7444                           return 1;
7445                      }
7446                 } else moveCount = 6;
7447             }
7448         if (appData.debugMode) { int i;
7449             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7450                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7451                     appData.drawRepeats);
7452             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7453               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7454
7455         }
7456
7457         // Repetition draws and 50-move rule can be applied independently of legality testing
7458
7459                 /* Check for rep-draws */
7460                 count = 0;
7461                 for(k = forwardMostMove-2;
7462                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7463                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7464                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7465                     k-=2)
7466                 {   int rights=0;
7467                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7468                         /* compare castling rights */
7469                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7470                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7471                                 rights++; /* King lost rights, while rook still had them */
7472                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7473                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7474                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7475                                    rights++; /* but at least one rook lost them */
7476                         }
7477                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7478                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7479                                 rights++;
7480                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7481                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7482                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7483                                    rights++;
7484                         }
7485                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7486                             && appData.drawRepeats > 1) {
7487                              /* adjudicate after user-specified nr of repeats */
7488                              int result = GameIsDrawn;
7489                              char *details = "XBoard adjudication: repetition draw";
7490                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7491                                 // [HGM] xiangqi: check for forbidden perpetuals
7492                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7493                                 for(m=forwardMostMove; m>k; m-=2) {
7494                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7495                                         ourPerpetual = 0; // the current mover did not always check
7496                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7497                                         hisPerpetual = 0; // the opponent did not always check
7498                                 }
7499                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7500                                                                         ourPerpetual, hisPerpetual);
7501                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7502                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7503                                     details = "Xboard adjudication: perpetual checking";
7504                                 } else
7505                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7506                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7507                                 } else
7508                                 // Now check for perpetual chases
7509                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7510                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7511                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7512                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7513                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7514                                         details = "Xboard adjudication: perpetual chasing";
7515                                     } else
7516                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7517                                         break; // Abort repetition-checking loop.
7518                                 }
7519                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7520                              }
7521                              if(engineOpponent) {
7522                                SendToProgram("force\n", engineOpponent); // suppress reply
7523                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7524                              }
7525                              GameEnds( result, details, GE_XBOARD );
7526                              return 1;
7527                         }
7528                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7529                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7530                     }
7531                 }
7532
7533                 /* Now we test for 50-move draws. Determine ply count */
7534                 count = forwardMostMove;
7535                 /* look for last irreversble move */
7536                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7537                     count--;
7538                 /* if we hit starting position, add initial plies */
7539                 if( count == backwardMostMove )
7540                     count -= initialRulePlies;
7541                 count = forwardMostMove - count;
7542                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7543                         // adjust reversible move counter for checks in Xiangqi
7544                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7545                         if(i < backwardMostMove) i = backwardMostMove;
7546                         while(i <= forwardMostMove) {
7547                                 lastCheck = inCheck; // check evasion does not count
7548                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7549                                 if(inCheck || lastCheck) count--; // check does not count
7550                                 i++;
7551                         }
7552                 }
7553                 if( count >= 100)
7554                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7555                          /* this is used to judge if draw claims are legal */
7556                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7557                          if(engineOpponent) {
7558                            SendToProgram("force\n", engineOpponent); // suppress reply
7559                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7560                          }
7561                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7562                          return 1;
7563                 }
7564
7565                 /* if draw offer is pending, treat it as a draw claim
7566                  * when draw condition present, to allow engines a way to
7567                  * claim draws before making their move to avoid a race
7568                  * condition occurring after their move
7569                  */
7570                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7571                          char *p = NULL;
7572                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7573                              p = "Draw claim: 50-move rule";
7574                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7575                              p = "Draw claim: 3-fold repetition";
7576                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7577                              p = "Draw claim: insufficient mating material";
7578                          if( p != NULL && canAdjudicate) {
7579                              if(engineOpponent) {
7580                                SendToProgram("force\n", engineOpponent); // suppress reply
7581                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7582                              }
7583                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7584                              return 1;
7585                          }
7586                 }
7587
7588                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7589                     if(engineOpponent) {
7590                       SendToProgram("force\n", engineOpponent); // suppress reply
7591                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7592                     }
7593                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7594                     return 1;
7595                 }
7596         return 0;
7597 }
7598
7599 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7600 {   // [HGM] book: this routine intercepts moves to simulate book replies
7601     char *bookHit = NULL;
7602
7603     //first determine if the incoming move brings opponent into his book
7604     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7605         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7606     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7607     if(bookHit != NULL && !cps->bookSuspend) {
7608         // make sure opponent is not going to reply after receiving move to book position
7609         SendToProgram("force\n", cps);
7610         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7611     }
7612     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7613     // now arrange restart after book miss
7614     if(bookHit) {
7615         // after a book hit we never send 'go', and the code after the call to this routine
7616         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7617         char buf[MSG_SIZ], *move = bookHit;
7618         if(cps->useSAN) {
7619             int fromX, fromY, toX, toY;
7620             char promoChar;
7621             ChessMove moveType;
7622             move = buf + 30;
7623             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7624                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7625                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7626                                     PosFlags(forwardMostMove),
7627                                     fromY, fromX, toY, toX, promoChar, move);
7628             } else {
7629                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7630                 bookHit = NULL;
7631             }
7632         }
7633         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7634         SendToProgram(buf, cps);
7635         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7636     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7637         SendToProgram("go\n", cps);
7638         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7639     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7640         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7641             SendToProgram("go\n", cps);
7642         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7643     }
7644     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7645 }
7646
7647 char *savedMessage;
7648 ChessProgramState *savedState;
7649 void DeferredBookMove(void)
7650 {
7651         if(savedState->lastPing != savedState->lastPong)
7652                     ScheduleDelayedEvent(DeferredBookMove, 10);
7653         else
7654         HandleMachineMove(savedMessage, savedState);
7655 }
7656
7657 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7658
7659 void
7660 HandleMachineMove(message, cps)
7661      char *message;
7662      ChessProgramState *cps;
7663 {
7664     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7665     char realname[MSG_SIZ];
7666     int fromX, fromY, toX, toY;
7667     ChessMove moveType;
7668     char promoChar;
7669     char *p;
7670     int machineWhite;
7671     char *bookHit;
7672
7673     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7674         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7675         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7676             DisplayError(_("Invalid pairing from pairing engine"), 0);
7677             return;
7678         }
7679         pairingReceived = 1;
7680         NextMatchGame();
7681         return; // Skim the pairing messages here.
7682     }
7683
7684     cps->userError = 0;
7685
7686 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7687     /*
7688      * Kludge to ignore BEL characters
7689      */
7690     while (*message == '\007') message++;
7691
7692     /*
7693      * [HGM] engine debug message: ignore lines starting with '#' character
7694      */
7695     if(cps->debug && *message == '#') return;
7696
7697     /*
7698      * Look for book output
7699      */
7700     if (cps == &first && bookRequested) {
7701         if (message[0] == '\t' || message[0] == ' ') {
7702             /* Part of the book output is here; append it */
7703             strcat(bookOutput, message);
7704             strcat(bookOutput, "  \n");
7705             return;
7706         } else if (bookOutput[0] != NULLCHAR) {
7707             /* All of book output has arrived; display it */
7708             char *p = bookOutput;
7709             while (*p != NULLCHAR) {
7710                 if (*p == '\t') *p = ' ';
7711                 p++;
7712             }
7713             DisplayInformation(bookOutput);
7714             bookRequested = FALSE;
7715             /* Fall through to parse the current output */
7716         }
7717     }
7718
7719     /*
7720      * Look for machine move.
7721      */
7722     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7723         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7724     {
7725         /* This method is only useful on engines that support ping */
7726         if (cps->lastPing != cps->lastPong) {
7727           if (gameMode == BeginningOfGame) {
7728             /* Extra move from before last new; ignore */
7729             if (appData.debugMode) {
7730                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7731             }
7732           } else {
7733             if (appData.debugMode) {
7734                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7735                         cps->which, gameMode);
7736             }
7737
7738             SendToProgram("undo\n", cps);
7739           }
7740           return;
7741         }
7742
7743         switch (gameMode) {
7744           case BeginningOfGame:
7745             /* Extra move from before last reset; ignore */
7746             if (appData.debugMode) {
7747                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7748             }
7749             return;
7750
7751           case EndOfGame:
7752           case IcsIdle:
7753           default:
7754             /* Extra move after we tried to stop.  The mode test is
7755                not a reliable way of detecting this problem, but it's
7756                the best we can do on engines that don't support ping.
7757             */
7758             if (appData.debugMode) {
7759                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7760                         cps->which, gameMode);
7761             }
7762             SendToProgram("undo\n", cps);
7763             return;
7764
7765           case MachinePlaysWhite:
7766           case IcsPlayingWhite:
7767             machineWhite = TRUE;
7768             break;
7769
7770           case MachinePlaysBlack:
7771           case IcsPlayingBlack:
7772             machineWhite = FALSE;
7773             break;
7774
7775           case TwoMachinesPlay:
7776             machineWhite = (cps->twoMachinesColor[0] == 'w');
7777             break;
7778         }
7779         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7780             if (appData.debugMode) {
7781                 fprintf(debugFP,
7782                         "Ignoring move out of turn by %s, gameMode %d"
7783                         ", forwardMost %d\n",
7784                         cps->which, gameMode, forwardMostMove);
7785             }
7786             return;
7787         }
7788
7789     if (appData.debugMode) { int f = forwardMostMove;
7790         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7791                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7792                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7793     }
7794         if(cps->alphaRank) AlphaRank(machineMove, 4);
7795         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7796                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7797             /* Machine move could not be parsed; ignore it. */
7798           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7799                     machineMove, _(cps->which));
7800             DisplayError(buf1, 0);
7801             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7802                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7803             if (gameMode == TwoMachinesPlay) {
7804               GameEnds(machineWhite ? BlackWins : WhiteWins,
7805                        buf1, GE_XBOARD);
7806             }
7807             return;
7808         }
7809
7810         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7811         /* So we have to redo legality test with true e.p. status here,  */
7812         /* to make sure an illegal e.p. capture does not slip through,   */
7813         /* to cause a forfeit on a justified illegal-move complaint      */
7814         /* of the opponent.                                              */
7815         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7816            ChessMove moveType;
7817            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7818                              fromY, fromX, toY, toX, promoChar);
7819             if (appData.debugMode) {
7820                 int i;
7821                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7822                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7823                 fprintf(debugFP, "castling rights\n");
7824             }
7825             if(moveType == IllegalMove) {
7826               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7827                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7828                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7829                            buf1, GE_XBOARD);
7830                 return;
7831            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7832            /* [HGM] Kludge to handle engines that send FRC-style castling
7833               when they shouldn't (like TSCP-Gothic) */
7834            switch(moveType) {
7835              case WhiteASideCastleFR:
7836              case BlackASideCastleFR:
7837                toX+=2;
7838                currentMoveString[2]++;
7839                break;
7840              case WhiteHSideCastleFR:
7841              case BlackHSideCastleFR:
7842                toX--;
7843                currentMoveString[2]--;
7844                break;
7845              default: ; // nothing to do, but suppresses warning of pedantic compilers
7846            }
7847         }
7848         hintRequested = FALSE;
7849         lastHint[0] = NULLCHAR;
7850         bookRequested = FALSE;
7851         /* Program may be pondering now */
7852         cps->maybeThinking = TRUE;
7853         if (cps->sendTime == 2) cps->sendTime = 1;
7854         if (cps->offeredDraw) cps->offeredDraw--;
7855
7856         /* [AS] Save move info*/
7857         pvInfoList[ forwardMostMove ].score = programStats.score;
7858         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7859         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7860
7861         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7862
7863         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7864         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7865             int count = 0;
7866
7867             while( count < adjudicateLossPlies ) {
7868                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7869
7870                 if( count & 1 ) {
7871                     score = -score; /* Flip score for winning side */
7872                 }
7873
7874                 if( score > adjudicateLossThreshold ) {
7875                     break;
7876                 }
7877
7878                 count++;
7879             }
7880
7881             if( count >= adjudicateLossPlies ) {
7882                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7883
7884                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7885                     "Xboard adjudication",
7886                     GE_XBOARD );
7887
7888                 return;
7889             }
7890         }
7891
7892         if(Adjudicate(cps)) {
7893             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7894             return; // [HGM] adjudicate: for all automatic game ends
7895         }
7896
7897 #if ZIPPY
7898         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7899             first.initDone) {
7900           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7901                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7902                 SendToICS("draw ");
7903                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7904           }
7905           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7906           ics_user_moved = 1;
7907           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7908                 char buf[3*MSG_SIZ];
7909
7910                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7911                         programStats.score / 100.,
7912                         programStats.depth,
7913                         programStats.time / 100.,
7914                         (unsigned int)programStats.nodes,
7915                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7916                         programStats.movelist);
7917                 SendToICS(buf);
7918 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7919           }
7920         }
7921 #endif
7922
7923         /* [AS] Clear stats for next move */
7924         ClearProgramStats();
7925         thinkOutput[0] = NULLCHAR;
7926         hiddenThinkOutputState = 0;
7927
7928         bookHit = NULL;
7929         if (gameMode == TwoMachinesPlay) {
7930             /* [HGM] relaying draw offers moved to after reception of move */
7931             /* and interpreting offer as claim if it brings draw condition */
7932             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7933                 SendToProgram("draw\n", cps->other);
7934             }
7935             if (cps->other->sendTime) {
7936                 SendTimeRemaining(cps->other,
7937                                   cps->other->twoMachinesColor[0] == 'w');
7938             }
7939             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7940             if (firstMove && !bookHit) {
7941                 firstMove = FALSE;
7942                 if (cps->other->useColors) {
7943                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7944                 }
7945                 SendToProgram("go\n", cps->other);
7946             }
7947             cps->other->maybeThinking = TRUE;
7948         }
7949
7950         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7951
7952         if (!pausing && appData.ringBellAfterMoves) {
7953             RingBell();
7954         }
7955
7956         /*
7957          * Reenable menu items that were disabled while
7958          * machine was thinking
7959          */
7960         if (gameMode != TwoMachinesPlay)
7961             SetUserThinkingEnables();
7962
7963         // [HGM] book: after book hit opponent has received move and is now in force mode
7964         // force the book reply into it, and then fake that it outputted this move by jumping
7965         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7966         if(bookHit) {
7967                 static char bookMove[MSG_SIZ]; // a bit generous?
7968
7969                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7970                 strcat(bookMove, bookHit);
7971                 message = bookMove;
7972                 cps = cps->other;
7973                 programStats.nodes = programStats.depth = programStats.time =
7974                 programStats.score = programStats.got_only_move = 0;
7975                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7976
7977                 if(cps->lastPing != cps->lastPong) {
7978                     savedMessage = message; // args for deferred call
7979                     savedState = cps;
7980                     ScheduleDelayedEvent(DeferredBookMove, 10);
7981                     return;
7982                 }
7983                 goto FakeBookMove;
7984         }
7985
7986         return;
7987     }
7988
7989     /* Set special modes for chess engines.  Later something general
7990      *  could be added here; for now there is just one kludge feature,
7991      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7992      *  when "xboard" is given as an interactive command.
7993      */
7994     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7995         cps->useSigint = FALSE;
7996         cps->useSigterm = FALSE;
7997     }
7998     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7999       ParseFeatures(message+8, cps);
8000       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8001     }
8002
8003     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8004       int dummy, s=6; char buf[MSG_SIZ];
8005       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8006       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8007       ParseFEN(boards[0], &dummy, message+s);
8008       DrawPosition(TRUE, boards[0]);
8009       startedFromSetupPosition = TRUE;
8010       return;
8011     }
8012     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8013      * want this, I was asked to put it in, and obliged.
8014      */
8015     if (!strncmp(message, "setboard ", 9)) {
8016         Board initial_position;
8017
8018         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8019
8020         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8021             DisplayError(_("Bad FEN received from engine"), 0);
8022             return ;
8023         } else {
8024            Reset(TRUE, FALSE);
8025            CopyBoard(boards[0], initial_position);
8026            initialRulePlies = FENrulePlies;
8027            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8028            else gameMode = MachinePlaysBlack;
8029            DrawPosition(FALSE, boards[currentMove]);
8030         }
8031         return;
8032     }
8033
8034     /*
8035      * Look for communication commands
8036      */
8037     if (!strncmp(message, "telluser ", 9)) {
8038         if(message[9] == '\\' && message[10] == '\\')
8039             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8040         DisplayNote(message + 9);
8041         return;
8042     }
8043     if (!strncmp(message, "tellusererror ", 14)) {
8044         cps->userError = 1;
8045         if(message[14] == '\\' && message[15] == '\\')
8046             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8047         DisplayError(message + 14, 0);
8048         return;
8049     }
8050     if (!strncmp(message, "tellopponent ", 13)) {
8051       if (appData.icsActive) {
8052         if (loggedOn) {
8053           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8054           SendToICS(buf1);
8055         }
8056       } else {
8057         DisplayNote(message + 13);
8058       }
8059       return;
8060     }
8061     if (!strncmp(message, "tellothers ", 11)) {
8062       if (appData.icsActive) {
8063         if (loggedOn) {
8064           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8065           SendToICS(buf1);
8066         }
8067       }
8068       return;
8069     }
8070     if (!strncmp(message, "tellall ", 8)) {
8071       if (appData.icsActive) {
8072         if (loggedOn) {
8073           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8074           SendToICS(buf1);
8075         }
8076       } else {
8077         DisplayNote(message + 8);
8078       }
8079       return;
8080     }
8081     if (strncmp(message, "warning", 7) == 0) {
8082         /* Undocumented feature, use tellusererror in new code */
8083         DisplayError(message, 0);
8084         return;
8085     }
8086     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8087         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8088         strcat(realname, " query");
8089         AskQuestion(realname, buf2, buf1, cps->pr);
8090         return;
8091     }
8092     /* Commands from the engine directly to ICS.  We don't allow these to be
8093      *  sent until we are logged on. Crafty kibitzes have been known to
8094      *  interfere with the login process.
8095      */
8096     if (loggedOn) {
8097         if (!strncmp(message, "tellics ", 8)) {
8098             SendToICS(message + 8);
8099             SendToICS("\n");
8100             return;
8101         }
8102         if (!strncmp(message, "tellicsnoalias ", 15)) {
8103             SendToICS(ics_prefix);
8104             SendToICS(message + 15);
8105             SendToICS("\n");
8106             return;
8107         }
8108         /* The following are for backward compatibility only */
8109         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8110             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8111             SendToICS(ics_prefix);
8112             SendToICS(message);
8113             SendToICS("\n");
8114             return;
8115         }
8116     }
8117     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8118         return;
8119     }
8120     /*
8121      * If the move is illegal, cancel it and redraw the board.
8122      * Also deal with other error cases.  Matching is rather loose
8123      * here to accommodate engines written before the spec.
8124      */
8125     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8126         strncmp(message, "Error", 5) == 0) {
8127         if (StrStr(message, "name") ||
8128             StrStr(message, "rating") || StrStr(message, "?") ||
8129             StrStr(message, "result") || StrStr(message, "board") ||
8130             StrStr(message, "bk") || StrStr(message, "computer") ||
8131             StrStr(message, "variant") || StrStr(message, "hint") ||
8132             StrStr(message, "random") || StrStr(message, "depth") ||
8133             StrStr(message, "accepted")) {
8134             return;
8135         }
8136         if (StrStr(message, "protover")) {
8137           /* Program is responding to input, so it's apparently done
8138              initializing, and this error message indicates it is
8139              protocol version 1.  So we don't need to wait any longer
8140              for it to initialize and send feature commands. */
8141           FeatureDone(cps, 1);
8142           cps->protocolVersion = 1;
8143           return;
8144         }
8145         cps->maybeThinking = FALSE;
8146
8147         if (StrStr(message, "draw")) {
8148             /* Program doesn't have "draw" command */
8149             cps->sendDrawOffers = 0;
8150             return;
8151         }
8152         if (cps->sendTime != 1 &&
8153             (StrStr(message, "time") || StrStr(message, "otim"))) {
8154           /* Program apparently doesn't have "time" or "otim" command */
8155           cps->sendTime = 0;
8156           return;
8157         }
8158         if (StrStr(message, "analyze")) {
8159             cps->analysisSupport = FALSE;
8160             cps->analyzing = FALSE;
8161             Reset(FALSE, TRUE);
8162             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8163             DisplayError(buf2, 0);
8164             return;
8165         }
8166         if (StrStr(message, "(no matching move)st")) {
8167           /* Special kludge for GNU Chess 4 only */
8168           cps->stKludge = TRUE;
8169           SendTimeControl(cps, movesPerSession, timeControl,
8170                           timeIncrement, appData.searchDepth,
8171                           searchTime);
8172           return;
8173         }
8174         if (StrStr(message, "(no matching move)sd")) {
8175           /* Special kludge for GNU Chess 4 only */
8176           cps->sdKludge = TRUE;
8177           SendTimeControl(cps, movesPerSession, timeControl,
8178                           timeIncrement, appData.searchDepth,
8179                           searchTime);
8180           return;
8181         }
8182         if (!StrStr(message, "llegal")) {
8183             return;
8184         }
8185         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8186             gameMode == IcsIdle) return;
8187         if (forwardMostMove <= backwardMostMove) return;
8188         if (pausing) PauseEvent();
8189       if(appData.forceIllegal) {
8190             // [HGM] illegal: machine refused move; force position after move into it
8191           SendToProgram("force\n", cps);
8192           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8193                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8194                 // when black is to move, while there might be nothing on a2 or black
8195                 // might already have the move. So send the board as if white has the move.
8196                 // But first we must change the stm of the engine, as it refused the last move
8197                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8198                 if(WhiteOnMove(forwardMostMove)) {
8199                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8200                     SendBoard(cps, forwardMostMove); // kludgeless board
8201                 } else {
8202                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8203                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8204                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8205                 }
8206           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8207             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8208                  gameMode == TwoMachinesPlay)
8209               SendToProgram("go\n", cps);
8210             return;
8211       } else
8212         if (gameMode == PlayFromGameFile) {
8213             /* Stop reading this game file */
8214             gameMode = EditGame;
8215             ModeHighlight();
8216         }
8217         /* [HGM] illegal-move claim should forfeit game when Xboard */
8218         /* only passes fully legal moves                            */
8219         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8220             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8221                                 "False illegal-move claim", GE_XBOARD );
8222             return; // do not take back move we tested as valid
8223         }
8224         currentMove = forwardMostMove-1;
8225         DisplayMove(currentMove-1); /* before DisplayMoveError */
8226         SwitchClocks(forwardMostMove-1); // [HGM] race
8227         DisplayBothClocks();
8228         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8229                 parseList[currentMove], _(cps->which));
8230         DisplayMoveError(buf1);
8231         DrawPosition(FALSE, boards[currentMove]);
8232         return;
8233     }
8234     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8235         /* Program has a broken "time" command that
8236            outputs a string not ending in newline.
8237            Don't use it. */
8238         cps->sendTime = 0;
8239     }
8240
8241     /*
8242      * If chess program startup fails, exit with an error message.
8243      * Attempts to recover here are futile.
8244      */
8245     if ((StrStr(message, "unknown host") != NULL)
8246         || (StrStr(message, "No remote directory") != NULL)
8247         || (StrStr(message, "not found") != NULL)
8248         || (StrStr(message, "No such file") != NULL)
8249         || (StrStr(message, "can't alloc") != NULL)
8250         || (StrStr(message, "Permission denied") != NULL)) {
8251
8252         cps->maybeThinking = FALSE;
8253         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8254                 _(cps->which), cps->program, cps->host, message);
8255         RemoveInputSource(cps->isr);
8256         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8257             if(cps == &first) appData.noChessProgram = TRUE;
8258             DisplayError(buf1, 0);
8259         }
8260         return;
8261     }
8262
8263     /*
8264      * Look for hint output
8265      */
8266     if (sscanf(message, "Hint: %s", buf1) == 1) {
8267         if (cps == &first && hintRequested) {
8268             hintRequested = FALSE;
8269             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8270                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8271                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8272                                     PosFlags(forwardMostMove),
8273                                     fromY, fromX, toY, toX, promoChar, buf1);
8274                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8275                 DisplayInformation(buf2);
8276             } else {
8277                 /* Hint move could not be parsed!? */
8278               snprintf(buf2, sizeof(buf2),
8279                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8280                         buf1, _(cps->which));
8281                 DisplayError(buf2, 0);
8282             }
8283         } else {
8284           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8285         }
8286         return;
8287     }
8288
8289     /*
8290      * Ignore other messages if game is not in progress
8291      */
8292     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8293         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8294
8295     /*
8296      * look for win, lose, draw, or draw offer
8297      */
8298     if (strncmp(message, "1-0", 3) == 0) {
8299         char *p, *q, *r = "";
8300         p = strchr(message, '{');
8301         if (p) {
8302             q = strchr(p, '}');
8303             if (q) {
8304                 *q = NULLCHAR;
8305                 r = p + 1;
8306             }
8307         }
8308         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8309         return;
8310     } else if (strncmp(message, "0-1", 3) == 0) {
8311         char *p, *q, *r = "";
8312         p = strchr(message, '{');
8313         if (p) {
8314             q = strchr(p, '}');
8315             if (q) {
8316                 *q = NULLCHAR;
8317                 r = p + 1;
8318             }
8319         }
8320         /* Kludge for Arasan 4.1 bug */
8321         if (strcmp(r, "Black resigns") == 0) {
8322             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8323             return;
8324         }
8325         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8326         return;
8327     } else if (strncmp(message, "1/2", 3) == 0) {
8328         char *p, *q, *r = "";
8329         p = strchr(message, '{');
8330         if (p) {
8331             q = strchr(p, '}');
8332             if (q) {
8333                 *q = NULLCHAR;
8334                 r = p + 1;
8335             }
8336         }
8337
8338         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8339         return;
8340
8341     } else if (strncmp(message, "White resign", 12) == 0) {
8342         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8343         return;
8344     } else if (strncmp(message, "Black resign", 12) == 0) {
8345         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8346         return;
8347     } else if (strncmp(message, "White matches", 13) == 0 ||
8348                strncmp(message, "Black matches", 13) == 0   ) {
8349         /* [HGM] ignore GNUShogi noises */
8350         return;
8351     } else if (strncmp(message, "White", 5) == 0 &&
8352                message[5] != '(' &&
8353                StrStr(message, "Black") == NULL) {
8354         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8355         return;
8356     } else if (strncmp(message, "Black", 5) == 0 &&
8357                message[5] != '(') {
8358         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8359         return;
8360     } else if (strcmp(message, "resign") == 0 ||
8361                strcmp(message, "computer resigns") == 0) {
8362         switch (gameMode) {
8363           case MachinePlaysBlack:
8364           case IcsPlayingBlack:
8365             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8366             break;
8367           case MachinePlaysWhite:
8368           case IcsPlayingWhite:
8369             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8370             break;
8371           case TwoMachinesPlay:
8372             if (cps->twoMachinesColor[0] == 'w')
8373               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8374             else
8375               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8376             break;
8377           default:
8378             /* can't happen */
8379             break;
8380         }
8381         return;
8382     } else if (strncmp(message, "opponent mates", 14) == 0) {
8383         switch (gameMode) {
8384           case MachinePlaysBlack:
8385           case IcsPlayingBlack:
8386             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8387             break;
8388           case MachinePlaysWhite:
8389           case IcsPlayingWhite:
8390             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8391             break;
8392           case TwoMachinesPlay:
8393             if (cps->twoMachinesColor[0] == 'w')
8394               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8395             else
8396               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8397             break;
8398           default:
8399             /* can't happen */
8400             break;
8401         }
8402         return;
8403     } else if (strncmp(message, "computer mates", 14) == 0) {
8404         switch (gameMode) {
8405           case MachinePlaysBlack:
8406           case IcsPlayingBlack:
8407             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8408             break;
8409           case MachinePlaysWhite:
8410           case IcsPlayingWhite:
8411             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8412             break;
8413           case TwoMachinesPlay:
8414             if (cps->twoMachinesColor[0] == 'w')
8415               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8416             else
8417               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8418             break;
8419           default:
8420             /* can't happen */
8421             break;
8422         }
8423         return;
8424     } else if (strncmp(message, "checkmate", 9) == 0) {
8425         if (WhiteOnMove(forwardMostMove)) {
8426             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8427         } else {
8428             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8429         }
8430         return;
8431     } else if (strstr(message, "Draw") != NULL ||
8432                strstr(message, "game is a draw") != NULL) {
8433         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8434         return;
8435     } else if (strstr(message, "offer") != NULL &&
8436                strstr(message, "draw") != NULL) {
8437 #if ZIPPY
8438         if (appData.zippyPlay && first.initDone) {
8439             /* Relay offer to ICS */
8440             SendToICS(ics_prefix);
8441             SendToICS("draw\n");
8442         }
8443 #endif
8444         cps->offeredDraw = 2; /* valid until this engine moves twice */
8445         if (gameMode == TwoMachinesPlay) {
8446             if (cps->other->offeredDraw) {
8447                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8448             /* [HGM] in two-machine mode we delay relaying draw offer      */
8449             /* until after we also have move, to see if it is really claim */
8450             }
8451         } else if (gameMode == MachinePlaysWhite ||
8452                    gameMode == MachinePlaysBlack) {
8453           if (userOfferedDraw) {
8454             DisplayInformation(_("Machine accepts your draw offer"));
8455             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8456           } else {
8457             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8458           }
8459         }
8460     }
8461
8462
8463     /*
8464      * Look for thinking output
8465      */
8466     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8467           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8468                                 ) {
8469         int plylev, mvleft, mvtot, curscore, time;
8470         char mvname[MOVE_LEN];
8471         u64 nodes; // [DM]
8472         char plyext;
8473         int ignore = FALSE;
8474         int prefixHint = FALSE;
8475         mvname[0] = NULLCHAR;
8476
8477         switch (gameMode) {
8478           case MachinePlaysBlack:
8479           case IcsPlayingBlack:
8480             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8481             break;
8482           case MachinePlaysWhite:
8483           case IcsPlayingWhite:
8484             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8485             break;
8486           case AnalyzeMode:
8487           case AnalyzeFile:
8488             break;
8489           case IcsObserving: /* [DM] icsEngineAnalyze */
8490             if (!appData.icsEngineAnalyze) ignore = TRUE;
8491             break;
8492           case TwoMachinesPlay:
8493             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8494                 ignore = TRUE;
8495             }
8496             break;
8497           default:
8498             ignore = TRUE;
8499             break;
8500         }
8501
8502         if (!ignore) {
8503             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8504             buf1[0] = NULLCHAR;
8505             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8506                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8507
8508                 if (plyext != ' ' && plyext != '\t') {
8509                     time *= 100;
8510                 }
8511
8512                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8513                 if( cps->scoreIsAbsolute &&
8514                     ( gameMode == MachinePlaysBlack ||
8515                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8516                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8517                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8518                      !WhiteOnMove(currentMove)
8519                     ) )
8520                 {
8521                     curscore = -curscore;
8522                 }
8523
8524
8525                 tempStats.depth = plylev;
8526                 tempStats.nodes = nodes;
8527                 tempStats.time = time;
8528                 tempStats.score = curscore;
8529                 tempStats.got_only_move = 0;
8530
8531                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8532                         int ticklen;
8533
8534                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8535                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8536                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8537                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8538                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8539                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8540                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8541                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8542                 }
8543
8544                 /* Buffer overflow protection */
8545                 if (buf1[0] != NULLCHAR) {
8546                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8547                         && appData.debugMode) {
8548                         fprintf(debugFP,
8549                                 "PV is too long; using the first %u bytes.\n",
8550                                 (unsigned) sizeof(tempStats.movelist) - 1);
8551                     }
8552
8553                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8554                 } else {
8555                     sprintf(tempStats.movelist, " no PV\n");
8556                 }
8557
8558                 if (tempStats.seen_stat) {
8559                     tempStats.ok_to_send = 1;
8560                 }
8561
8562                 if (strchr(tempStats.movelist, '(') != NULL) {
8563                     tempStats.line_is_book = 1;
8564                     tempStats.nr_moves = 0;
8565                     tempStats.moves_left = 0;
8566                 } else {
8567                     tempStats.line_is_book = 0;
8568                 }
8569
8570                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8571                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8572
8573                 SendProgramStatsToFrontend( cps, &tempStats );
8574
8575                 /*
8576                     [AS] Protect the thinkOutput buffer from overflow... this
8577                     is only useful if buf1 hasn't overflowed first!
8578                 */
8579                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8580                          plylev,
8581                          (gameMode == TwoMachinesPlay ?
8582                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8583                          ((double) curscore) / 100.0,
8584                          prefixHint ? lastHint : "",
8585                          prefixHint ? " " : "" );
8586
8587                 if( buf1[0] != NULLCHAR ) {
8588                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8589
8590                     if( strlen(buf1) > max_len ) {
8591                         if( appData.debugMode) {
8592                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8593                         }
8594                         buf1[max_len+1] = '\0';
8595                     }
8596
8597                     strcat( thinkOutput, buf1 );
8598                 }
8599
8600                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8601                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8602                     DisplayMove(currentMove - 1);
8603                 }
8604                 return;
8605
8606             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8607                 /* crafty (9.25+) says "(only move) <move>"
8608                  * if there is only 1 legal move
8609                  */
8610                 sscanf(p, "(only move) %s", buf1);
8611                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8612                 sprintf(programStats.movelist, "%s (only move)", buf1);
8613                 programStats.depth = 1;
8614                 programStats.nr_moves = 1;
8615                 programStats.moves_left = 1;
8616                 programStats.nodes = 1;
8617                 programStats.time = 1;
8618                 programStats.got_only_move = 1;
8619
8620                 /* Not really, but we also use this member to
8621                    mean "line isn't going to change" (Crafty
8622                    isn't searching, so stats won't change) */
8623                 programStats.line_is_book = 1;
8624
8625                 SendProgramStatsToFrontend( cps, &programStats );
8626
8627                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8628                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8629                     DisplayMove(currentMove - 1);
8630                 }
8631                 return;
8632             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8633                               &time, &nodes, &plylev, &mvleft,
8634                               &mvtot, mvname) >= 5) {
8635                 /* The stat01: line is from Crafty (9.29+) in response
8636                    to the "." command */
8637                 programStats.seen_stat = 1;
8638                 cps->maybeThinking = TRUE;
8639
8640                 if (programStats.got_only_move || !appData.periodicUpdates)
8641                   return;
8642
8643                 programStats.depth = plylev;
8644                 programStats.time = time;
8645                 programStats.nodes = nodes;
8646                 programStats.moves_left = mvleft;
8647                 programStats.nr_moves = mvtot;
8648                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8649                 programStats.ok_to_send = 1;
8650                 programStats.movelist[0] = '\0';
8651
8652                 SendProgramStatsToFrontend( cps, &programStats );
8653
8654                 return;
8655
8656             } else if (strncmp(message,"++",2) == 0) {
8657                 /* Crafty 9.29+ outputs this */
8658                 programStats.got_fail = 2;
8659                 return;
8660
8661             } else if (strncmp(message,"--",2) == 0) {
8662                 /* Crafty 9.29+ outputs this */
8663                 programStats.got_fail = 1;
8664                 return;
8665
8666             } else if (thinkOutput[0] != NULLCHAR &&
8667                        strncmp(message, "    ", 4) == 0) {
8668                 unsigned message_len;
8669
8670                 p = message;
8671                 while (*p && *p == ' ') p++;
8672
8673                 message_len = strlen( p );
8674
8675                 /* [AS] Avoid buffer overflow */
8676                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8677                     strcat(thinkOutput, " ");
8678                     strcat(thinkOutput, p);
8679                 }
8680
8681                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8682                     strcat(programStats.movelist, " ");
8683                     strcat(programStats.movelist, p);
8684                 }
8685
8686                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8687                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8688                     DisplayMove(currentMove - 1);
8689                 }
8690                 return;
8691             }
8692         }
8693         else {
8694             buf1[0] = NULLCHAR;
8695
8696             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8697                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8698             {
8699                 ChessProgramStats cpstats;
8700
8701                 if (plyext != ' ' && plyext != '\t') {
8702                     time *= 100;
8703                 }
8704
8705                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8706                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8707                     curscore = -curscore;
8708                 }
8709
8710                 cpstats.depth = plylev;
8711                 cpstats.nodes = nodes;
8712                 cpstats.time = time;
8713                 cpstats.score = curscore;
8714                 cpstats.got_only_move = 0;
8715                 cpstats.movelist[0] = '\0';
8716
8717                 if (buf1[0] != NULLCHAR) {
8718                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8719                 }
8720
8721                 cpstats.ok_to_send = 0;
8722                 cpstats.line_is_book = 0;
8723                 cpstats.nr_moves = 0;
8724                 cpstats.moves_left = 0;
8725
8726                 SendProgramStatsToFrontend( cps, &cpstats );
8727             }
8728         }
8729     }
8730 }
8731
8732
8733 /* Parse a game score from the character string "game", and
8734    record it as the history of the current game.  The game
8735    score is NOT assumed to start from the standard position.
8736    The display is not updated in any way.
8737    */
8738 void
8739 ParseGameHistory(game)
8740      char *game;
8741 {
8742     ChessMove moveType;
8743     int fromX, fromY, toX, toY, boardIndex;
8744     char promoChar;
8745     char *p, *q;
8746     char buf[MSG_SIZ];
8747
8748     if (appData.debugMode)
8749       fprintf(debugFP, "Parsing game history: %s\n", game);
8750
8751     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8752     gameInfo.site = StrSave(appData.icsHost);
8753     gameInfo.date = PGNDate();
8754     gameInfo.round = StrSave("-");
8755
8756     /* Parse out names of players */
8757     while (*game == ' ') game++;
8758     p = buf;
8759     while (*game != ' ') *p++ = *game++;
8760     *p = NULLCHAR;
8761     gameInfo.white = StrSave(buf);
8762     while (*game == ' ') game++;
8763     p = buf;
8764     while (*game != ' ' && *game != '\n') *p++ = *game++;
8765     *p = NULLCHAR;
8766     gameInfo.black = StrSave(buf);
8767
8768     /* Parse moves */
8769     boardIndex = blackPlaysFirst ? 1 : 0;
8770     yynewstr(game);
8771     for (;;) {
8772         yyboardindex = boardIndex;
8773         moveType = (ChessMove) Myylex();
8774         switch (moveType) {
8775           case IllegalMove:             /* maybe suicide chess, etc. */
8776   if (appData.debugMode) {
8777     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8778     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8779     setbuf(debugFP, NULL);
8780   }
8781           case WhitePromotion:
8782           case BlackPromotion:
8783           case WhiteNonPromotion:
8784           case BlackNonPromotion:
8785           case NormalMove:
8786           case WhiteCapturesEnPassant:
8787           case BlackCapturesEnPassant:
8788           case WhiteKingSideCastle:
8789           case WhiteQueenSideCastle:
8790           case BlackKingSideCastle:
8791           case BlackQueenSideCastle:
8792           case WhiteKingSideCastleWild:
8793           case WhiteQueenSideCastleWild:
8794           case BlackKingSideCastleWild:
8795           case BlackQueenSideCastleWild:
8796           /* PUSH Fabien */
8797           case WhiteHSideCastleFR:
8798           case WhiteASideCastleFR:
8799           case BlackHSideCastleFR:
8800           case BlackASideCastleFR:
8801           /* POP Fabien */
8802             fromX = currentMoveString[0] - AAA;
8803             fromY = currentMoveString[1] - ONE;
8804             toX = currentMoveString[2] - AAA;
8805             toY = currentMoveString[3] - ONE;
8806             promoChar = currentMoveString[4];
8807             break;
8808           case WhiteDrop:
8809           case BlackDrop:
8810             fromX = moveType == WhiteDrop ?
8811               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8812             (int) CharToPiece(ToLower(currentMoveString[0]));
8813             fromY = DROP_RANK;
8814             toX = currentMoveString[2] - AAA;
8815             toY = currentMoveString[3] - ONE;
8816             promoChar = NULLCHAR;
8817             break;
8818           case AmbiguousMove:
8819             /* bug? */
8820             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8821   if (appData.debugMode) {
8822     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8823     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8824     setbuf(debugFP, NULL);
8825   }
8826             DisplayError(buf, 0);
8827             return;
8828           case ImpossibleMove:
8829             /* bug? */
8830             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8831   if (appData.debugMode) {
8832     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8833     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8834     setbuf(debugFP, NULL);
8835   }
8836             DisplayError(buf, 0);
8837             return;
8838           case EndOfFile:
8839             if (boardIndex < backwardMostMove) {
8840                 /* Oops, gap.  How did that happen? */
8841                 DisplayError(_("Gap in move list"), 0);
8842                 return;
8843             }
8844             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8845             if (boardIndex > forwardMostMove) {
8846                 forwardMostMove = boardIndex;
8847             }
8848             return;
8849           case ElapsedTime:
8850             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8851                 strcat(parseList[boardIndex-1], " ");
8852                 strcat(parseList[boardIndex-1], yy_text);
8853             }
8854             continue;
8855           case Comment:
8856           case PGNTag:
8857           case NAG:
8858           default:
8859             /* ignore */
8860             continue;
8861           case WhiteWins:
8862           case BlackWins:
8863           case GameIsDrawn:
8864           case GameUnfinished:
8865             if (gameMode == IcsExamining) {
8866                 if (boardIndex < backwardMostMove) {
8867                     /* Oops, gap.  How did that happen? */
8868                     return;
8869                 }
8870                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8871                 return;
8872             }
8873             gameInfo.result = moveType;
8874             p = strchr(yy_text, '{');
8875             if (p == NULL) p = strchr(yy_text, '(');
8876             if (p == NULL) {
8877                 p = yy_text;
8878                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8879             } else {
8880                 q = strchr(p, *p == '{' ? '}' : ')');
8881                 if (q != NULL) *q = NULLCHAR;
8882                 p++;
8883             }
8884             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8885             gameInfo.resultDetails = StrSave(p);
8886             continue;
8887         }
8888         if (boardIndex >= forwardMostMove &&
8889             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8890             backwardMostMove = blackPlaysFirst ? 1 : 0;
8891             return;
8892         }
8893         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8894                                  fromY, fromX, toY, toX, promoChar,
8895                                  parseList[boardIndex]);
8896         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8897         /* currentMoveString is set as a side-effect of yylex */
8898         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8899         strcat(moveList[boardIndex], "\n");
8900         boardIndex++;
8901         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8902         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8903           case MT_NONE:
8904           case MT_STALEMATE:
8905           default:
8906             break;
8907           case MT_CHECK:
8908             if(gameInfo.variant != VariantShogi)
8909                 strcat(parseList[boardIndex - 1], "+");
8910             break;
8911           case MT_CHECKMATE:
8912           case MT_STAINMATE:
8913             strcat(parseList[boardIndex - 1], "#");
8914             break;
8915         }
8916     }
8917 }
8918
8919
8920 /* Apply a move to the given board  */
8921 void
8922 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8923      int fromX, fromY, toX, toY;
8924      int promoChar;
8925      Board board;
8926 {
8927   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8928   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8929
8930     /* [HGM] compute & store e.p. status and castling rights for new position */
8931     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8932
8933       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8934       oldEP = (signed char)board[EP_STATUS];
8935       board[EP_STATUS] = EP_NONE;
8936
8937       if( board[toY][toX] != EmptySquare )
8938            board[EP_STATUS] = EP_CAPTURE;
8939
8940   if (fromY == DROP_RANK) {
8941         /* must be first */
8942         piece = board[toY][toX] = (ChessSquare) fromX;
8943   } else {
8944       int i;
8945
8946       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8947            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8948                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8949       } else
8950       if( board[fromY][fromX] == WhitePawn ) {
8951            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8952                board[EP_STATUS] = EP_PAWN_MOVE;
8953            if( toY-fromY==2) {
8954                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8955                         gameInfo.variant != VariantBerolina || toX < fromX)
8956                       board[EP_STATUS] = toX | berolina;
8957                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8958                         gameInfo.variant != VariantBerolina || toX > fromX)
8959                       board[EP_STATUS] = toX;
8960            }
8961       } else
8962       if( board[fromY][fromX] == BlackPawn ) {
8963            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8964                board[EP_STATUS] = EP_PAWN_MOVE;
8965            if( toY-fromY== -2) {
8966                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8967                         gameInfo.variant != VariantBerolina || toX < fromX)
8968                       board[EP_STATUS] = toX | berolina;
8969                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8970                         gameInfo.variant != VariantBerolina || toX > fromX)
8971                       board[EP_STATUS] = toX;
8972            }
8973        }
8974
8975        for(i=0; i<nrCastlingRights; i++) {
8976            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8977               board[CASTLING][i] == toX   && castlingRank[i] == toY
8978              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8979        }
8980
8981      if (fromX == toX && fromY == toY) return;
8982
8983      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8984      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8985      if(gameInfo.variant == VariantKnightmate)
8986          king += (int) WhiteUnicorn - (int) WhiteKing;
8987
8988     /* Code added by Tord: */
8989     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8990     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8991         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8992       board[fromY][fromX] = EmptySquare;
8993       board[toY][toX] = EmptySquare;
8994       if((toX > fromX) != (piece == WhiteRook)) {
8995         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8996       } else {
8997         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8998       }
8999     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9000                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9001       board[fromY][fromX] = EmptySquare;
9002       board[toY][toX] = EmptySquare;
9003       if((toX > fromX) != (piece == BlackRook)) {
9004         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9005       } else {
9006         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9007       }
9008     /* End of code added by Tord */
9009
9010     } else if (board[fromY][fromX] == king
9011         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9012         && toY == fromY && toX > fromX+1) {
9013         board[fromY][fromX] = EmptySquare;
9014         board[toY][toX] = king;
9015         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9016         board[fromY][BOARD_RGHT-1] = EmptySquare;
9017     } else if (board[fromY][fromX] == king
9018         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9019                && toY == fromY && toX < fromX-1) {
9020         board[fromY][fromX] = EmptySquare;
9021         board[toY][toX] = king;
9022         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9023         board[fromY][BOARD_LEFT] = EmptySquare;
9024     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9025                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9026                && toY >= BOARD_HEIGHT-promoRank
9027                ) {
9028         /* white pawn promotion */
9029         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9030         if (board[toY][toX] == EmptySquare) {
9031             board[toY][toX] = WhiteQueen;
9032         }
9033         if(gameInfo.variant==VariantBughouse ||
9034            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9035             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9036         board[fromY][fromX] = EmptySquare;
9037     } else if ((fromY == BOARD_HEIGHT-4)
9038                && (toX != fromX)
9039                && gameInfo.variant != VariantXiangqi
9040                && gameInfo.variant != VariantBerolina
9041                && (board[fromY][fromX] == WhitePawn)
9042                && (board[toY][toX] == EmptySquare)) {
9043         board[fromY][fromX] = EmptySquare;
9044         board[toY][toX] = WhitePawn;
9045         captured = board[toY - 1][toX];
9046         board[toY - 1][toX] = EmptySquare;
9047     } else if ((fromY == BOARD_HEIGHT-4)
9048                && (toX == fromX)
9049                && gameInfo.variant == VariantBerolina
9050                && (board[fromY][fromX] == WhitePawn)
9051                && (board[toY][toX] == EmptySquare)) {
9052         board[fromY][fromX] = EmptySquare;
9053         board[toY][toX] = WhitePawn;
9054         if(oldEP & EP_BEROLIN_A) {
9055                 captured = board[fromY][fromX-1];
9056                 board[fromY][fromX-1] = EmptySquare;
9057         }else{  captured = board[fromY][fromX+1];
9058                 board[fromY][fromX+1] = EmptySquare;
9059         }
9060     } else if (board[fromY][fromX] == king
9061         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9062                && toY == fromY && toX > fromX+1) {
9063         board[fromY][fromX] = EmptySquare;
9064         board[toY][toX] = king;
9065         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9066         board[fromY][BOARD_RGHT-1] = EmptySquare;
9067     } else if (board[fromY][fromX] == king
9068         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9069                && toY == fromY && toX < fromX-1) {
9070         board[fromY][fromX] = EmptySquare;
9071         board[toY][toX] = king;
9072         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9073         board[fromY][BOARD_LEFT] = EmptySquare;
9074     } else if (fromY == 7 && fromX == 3
9075                && board[fromY][fromX] == BlackKing
9076                && toY == 7 && toX == 5) {
9077         board[fromY][fromX] = EmptySquare;
9078         board[toY][toX] = BlackKing;
9079         board[fromY][7] = EmptySquare;
9080         board[toY][4] = BlackRook;
9081     } else if (fromY == 7 && fromX == 3
9082                && board[fromY][fromX] == BlackKing
9083                && toY == 7 && toX == 1) {
9084         board[fromY][fromX] = EmptySquare;
9085         board[toY][toX] = BlackKing;
9086         board[fromY][0] = EmptySquare;
9087         board[toY][2] = BlackRook;
9088     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9089                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9090                && toY < promoRank
9091                ) {
9092         /* black pawn promotion */
9093         board[toY][toX] = CharToPiece(ToLower(promoChar));
9094         if (board[toY][toX] == EmptySquare) {
9095             board[toY][toX] = BlackQueen;
9096         }
9097         if(gameInfo.variant==VariantBughouse ||
9098            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9099             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9100         board[fromY][fromX] = EmptySquare;
9101     } else if ((fromY == 3)
9102                && (toX != fromX)
9103                && gameInfo.variant != VariantXiangqi
9104                && gameInfo.variant != VariantBerolina
9105                && (board[fromY][fromX] == BlackPawn)
9106                && (board[toY][toX] == EmptySquare)) {
9107         board[fromY][fromX] = EmptySquare;
9108         board[toY][toX] = BlackPawn;
9109         captured = board[toY + 1][toX];
9110         board[toY + 1][toX] = EmptySquare;
9111     } else if ((fromY == 3)
9112                && (toX == fromX)
9113                && gameInfo.variant == VariantBerolina
9114                && (board[fromY][fromX] == BlackPawn)
9115                && (board[toY][toX] == EmptySquare)) {
9116         board[fromY][fromX] = EmptySquare;
9117         board[toY][toX] = BlackPawn;
9118         if(oldEP & EP_BEROLIN_A) {
9119                 captured = board[fromY][fromX-1];
9120                 board[fromY][fromX-1] = EmptySquare;
9121         }else{  captured = board[fromY][fromX+1];
9122                 board[fromY][fromX+1] = EmptySquare;
9123         }
9124     } else {
9125         board[toY][toX] = board[fromY][fromX];
9126         board[fromY][fromX] = EmptySquare;
9127     }
9128   }
9129
9130     if (gameInfo.holdingsWidth != 0) {
9131
9132       /* !!A lot more code needs to be written to support holdings  */
9133       /* [HGM] OK, so I have written it. Holdings are stored in the */
9134       /* penultimate board files, so they are automaticlly stored   */
9135       /* in the game history.                                       */
9136       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9137                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9138         /* Delete from holdings, by decreasing count */
9139         /* and erasing image if necessary            */
9140         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9141         if(p < (int) BlackPawn) { /* white drop */
9142              p -= (int)WhitePawn;
9143                  p = PieceToNumber((ChessSquare)p);
9144              if(p >= gameInfo.holdingsSize) p = 0;
9145              if(--board[p][BOARD_WIDTH-2] <= 0)
9146                   board[p][BOARD_WIDTH-1] = EmptySquare;
9147              if((int)board[p][BOARD_WIDTH-2] < 0)
9148                         board[p][BOARD_WIDTH-2] = 0;
9149         } else {                  /* black drop */
9150              p -= (int)BlackPawn;
9151                  p = PieceToNumber((ChessSquare)p);
9152              if(p >= gameInfo.holdingsSize) p = 0;
9153              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9154                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9155              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9156                         board[BOARD_HEIGHT-1-p][1] = 0;
9157         }
9158       }
9159       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9160           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9161         /* [HGM] holdings: Add to holdings, if holdings exist */
9162         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9163                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9164                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9165         }
9166         p = (int) captured;
9167         if (p >= (int) BlackPawn) {
9168           p -= (int)BlackPawn;
9169           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9170                   /* in Shogi restore piece to its original  first */
9171                   captured = (ChessSquare) (DEMOTED captured);
9172                   p = DEMOTED p;
9173           }
9174           p = PieceToNumber((ChessSquare)p);
9175           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9176           board[p][BOARD_WIDTH-2]++;
9177           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9178         } else {
9179           p -= (int)WhitePawn;
9180           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9181                   captured = (ChessSquare) (DEMOTED captured);
9182                   p = DEMOTED p;
9183           }
9184           p = PieceToNumber((ChessSquare)p);
9185           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9186           board[BOARD_HEIGHT-1-p][1]++;
9187           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9188         }
9189       }
9190     } else if (gameInfo.variant == VariantAtomic) {
9191       if (captured != EmptySquare) {
9192         int y, x;
9193         for (y = toY-1; y <= toY+1; y++) {
9194           for (x = toX-1; x <= toX+1; x++) {
9195             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9196                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9197               board[y][x] = EmptySquare;
9198             }
9199           }
9200         }
9201         board[toY][toX] = EmptySquare;
9202       }
9203     }
9204     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9205         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9206     } else
9207     if(promoChar == '+') {
9208         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9209         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9210     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9211         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9212     }
9213     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9214                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9215         // [HGM] superchess: take promotion piece out of holdings
9216         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9217         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9218             if(!--board[k][BOARD_WIDTH-2])
9219                 board[k][BOARD_WIDTH-1] = EmptySquare;
9220         } else {
9221             if(!--board[BOARD_HEIGHT-1-k][1])
9222                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9223         }
9224     }
9225
9226 }
9227
9228 /* Updates forwardMostMove */
9229 void
9230 MakeMove(fromX, fromY, toX, toY, promoChar)
9231      int fromX, fromY, toX, toY;
9232      int promoChar;
9233 {
9234 //    forwardMostMove++; // [HGM] bare: moved downstream
9235
9236     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9237         int timeLeft; static int lastLoadFlag=0; int king, piece;
9238         piece = boards[forwardMostMove][fromY][fromX];
9239         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9240         if(gameInfo.variant == VariantKnightmate)
9241             king += (int) WhiteUnicorn - (int) WhiteKing;
9242         if(forwardMostMove == 0) {
9243             if(blackPlaysFirst)
9244                 fprintf(serverMoves, "%s;", second.tidy);
9245             fprintf(serverMoves, "%s;", first.tidy);
9246             if(!blackPlaysFirst)
9247                 fprintf(serverMoves, "%s;", second.tidy);
9248         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9249         lastLoadFlag = loadFlag;
9250         // print base move
9251         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9252         // print castling suffix
9253         if( toY == fromY && piece == king ) {
9254             if(toX-fromX > 1)
9255                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9256             if(fromX-toX >1)
9257                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9258         }
9259         // e.p. suffix
9260         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9261              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9262              boards[forwardMostMove][toY][toX] == EmptySquare
9263              && fromX != toX && fromY != toY)
9264                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9265         // promotion suffix
9266         if(promoChar != NULLCHAR)
9267                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9268         if(!loadFlag) {
9269             fprintf(serverMoves, "/%d/%d",
9270                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9271             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9272             else                      timeLeft = blackTimeRemaining/1000;
9273             fprintf(serverMoves, "/%d", timeLeft);
9274         }
9275         fflush(serverMoves);
9276     }
9277
9278     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9279       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9280                         0, 1);
9281       return;
9282     }
9283     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9284     if (commentList[forwardMostMove+1] != NULL) {
9285         free(commentList[forwardMostMove+1]);
9286         commentList[forwardMostMove+1] = NULL;
9287     }
9288     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9289     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9290     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9291     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9292     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9293     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9294     gameInfo.result = GameUnfinished;
9295     if (gameInfo.resultDetails != NULL) {
9296         free(gameInfo.resultDetails);
9297         gameInfo.resultDetails = NULL;
9298     }
9299     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9300                               moveList[forwardMostMove - 1]);
9301     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9302                              PosFlags(forwardMostMove - 1),
9303                              fromY, fromX, toY, toX, promoChar,
9304                              parseList[forwardMostMove - 1]);
9305     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9306       case MT_NONE:
9307       case MT_STALEMATE:
9308       default:
9309         break;
9310       case MT_CHECK:
9311         if(gameInfo.variant != VariantShogi)
9312             strcat(parseList[forwardMostMove - 1], "+");
9313         break;
9314       case MT_CHECKMATE:
9315       case MT_STAINMATE:
9316         strcat(parseList[forwardMostMove - 1], "#");
9317         break;
9318     }
9319     if (appData.debugMode) {
9320         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9321     }
9322
9323 }
9324
9325 /* Updates currentMove if not pausing */
9326 void
9327 ShowMove(fromX, fromY, toX, toY)
9328 {
9329     int instant = (gameMode == PlayFromGameFile) ?
9330         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9331     if(appData.noGUI) return;
9332     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9333         if (!instant) {
9334             if (forwardMostMove == currentMove + 1) {
9335                 AnimateMove(boards[forwardMostMove - 1],
9336                             fromX, fromY, toX, toY);
9337             }
9338             if (appData.highlightLastMove) {
9339                 SetHighlights(fromX, fromY, toX, toY);
9340             }
9341         }
9342         currentMove = forwardMostMove;
9343     }
9344
9345     if (instant) return;
9346
9347     DisplayMove(currentMove - 1);
9348     DrawPosition(FALSE, boards[currentMove]);
9349     DisplayBothClocks();
9350     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9351     DisplayBook(currentMove);
9352 }
9353
9354 void SendEgtPath(ChessProgramState *cps)
9355 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9356         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9357
9358         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9359
9360         while(*p) {
9361             char c, *q = name+1, *r, *s;
9362
9363             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9364             while(*p && *p != ',') *q++ = *p++;
9365             *q++ = ':'; *q = 0;
9366             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9367                 strcmp(name, ",nalimov:") == 0 ) {
9368                 // take nalimov path from the menu-changeable option first, if it is defined
9369               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9370                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9371             } else
9372             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9373                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9374                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9375                 s = r = StrStr(s, ":") + 1; // beginning of path info
9376                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9377                 c = *r; *r = 0;             // temporarily null-terminate path info
9378                     *--q = 0;               // strip of trailig ':' from name
9379                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9380                 *r = c;
9381                 SendToProgram(buf,cps);     // send egtbpath command for this format
9382             }
9383             if(*p == ',') p++; // read away comma to position for next format name
9384         }
9385 }
9386
9387 void
9388 InitChessProgram(cps, setup)
9389      ChessProgramState *cps;
9390      int setup; /* [HGM] needed to setup FRC opening position */
9391 {
9392     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9393     if (appData.noChessProgram) return;
9394     hintRequested = FALSE;
9395     bookRequested = FALSE;
9396
9397     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9398     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9399     if(cps->memSize) { /* [HGM] memory */
9400       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9401         SendToProgram(buf, cps);
9402     }
9403     SendEgtPath(cps); /* [HGM] EGT */
9404     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9405       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9406         SendToProgram(buf, cps);
9407     }
9408
9409     SendToProgram(cps->initString, cps);
9410     if (gameInfo.variant != VariantNormal &&
9411         gameInfo.variant != VariantLoadable
9412         /* [HGM] also send variant if board size non-standard */
9413         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9414                                             ) {
9415       char *v = VariantName(gameInfo.variant);
9416       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9417         /* [HGM] in protocol 1 we have to assume all variants valid */
9418         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9419         DisplayFatalError(buf, 0, 1);
9420         return;
9421       }
9422
9423       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9424       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9425       if( gameInfo.variant == VariantXiangqi )
9426            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9427       if( gameInfo.variant == VariantShogi )
9428            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9429       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9430            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9431       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9432           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9433            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9434       if( gameInfo.variant == VariantCourier )
9435            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9436       if( gameInfo.variant == VariantSuper )
9437            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9438       if( gameInfo.variant == VariantGreat )
9439            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9440       if( gameInfo.variant == VariantSChess )
9441            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9442
9443       if(overruled) {
9444         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9445                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9446            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9447            if(StrStr(cps->variants, b) == NULL) {
9448                // specific sized variant not known, check if general sizing allowed
9449                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9450                    if(StrStr(cps->variants, "boardsize") == NULL) {
9451                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9452                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9453                        DisplayFatalError(buf, 0, 1);
9454                        return;
9455                    }
9456                    /* [HGM] here we really should compare with the maximum supported board size */
9457                }
9458            }
9459       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9460       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9461       SendToProgram(buf, cps);
9462     }
9463     currentlyInitializedVariant = gameInfo.variant;
9464
9465     /* [HGM] send opening position in FRC to first engine */
9466     if(setup) {
9467           SendToProgram("force\n", cps);
9468           SendBoard(cps, 0);
9469           /* engine is now in force mode! Set flag to wake it up after first move. */
9470           setboardSpoiledMachineBlack = 1;
9471     }
9472
9473     if (cps->sendICS) {
9474       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9475       SendToProgram(buf, cps);
9476     }
9477     cps->maybeThinking = FALSE;
9478     cps->offeredDraw = 0;
9479     if (!appData.icsActive) {
9480         SendTimeControl(cps, movesPerSession, timeControl,
9481                         timeIncrement, appData.searchDepth,
9482                         searchTime);
9483     }
9484     if (appData.showThinking
9485         // [HGM] thinking: four options require thinking output to be sent
9486         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9487                                 ) {
9488         SendToProgram("post\n", cps);
9489     }
9490     SendToProgram("hard\n", cps);
9491     if (!appData.ponderNextMove) {
9492         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9493            it without being sure what state we are in first.  "hard"
9494            is not a toggle, so that one is OK.
9495          */
9496         SendToProgram("easy\n", cps);
9497     }
9498     if (cps->usePing) {
9499       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9500       SendToProgram(buf, cps);
9501     }
9502     cps->initDone = TRUE;
9503     ClearEngineOutputPane(cps == &second);
9504 }
9505
9506
9507 void
9508 StartChessProgram(cps)
9509      ChessProgramState *cps;
9510 {
9511     char buf[MSG_SIZ];
9512     int err;
9513
9514     if (appData.noChessProgram) return;
9515     cps->initDone = FALSE;
9516
9517     if (strcmp(cps->host, "localhost") == 0) {
9518         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9519     } else if (*appData.remoteShell == NULLCHAR) {
9520         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9521     } else {
9522         if (*appData.remoteUser == NULLCHAR) {
9523           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9524                     cps->program);
9525         } else {
9526           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9527                     cps->host, appData.remoteUser, cps->program);
9528         }
9529         err = StartChildProcess(buf, "", &cps->pr);
9530     }
9531
9532     if (err != 0) {
9533       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9534         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9535         if(cps != &first) return;
9536         appData.noChessProgram = TRUE;
9537         ThawUI();
9538         SetNCPMode();
9539 //      DisplayFatalError(buf, err, 1);
9540 //      cps->pr = NoProc;
9541 //      cps->isr = NULL;
9542         return;
9543     }
9544
9545     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9546     if (cps->protocolVersion > 1) {
9547       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9548       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9549       cps->comboCnt = 0;  //                and values of combo boxes
9550       SendToProgram(buf, cps);
9551     } else {
9552       SendToProgram("xboard\n", cps);
9553     }
9554 }
9555
9556 void
9557 TwoMachinesEventIfReady P((void))
9558 {
9559   static int curMess = 0;
9560   if (first.lastPing != first.lastPong) {
9561     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9562     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9563     return;
9564   }
9565   if (second.lastPing != second.lastPong) {
9566     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9567     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9568     return;
9569   }
9570   DisplayMessage("", ""); curMess = 0;
9571   ThawUI();
9572   TwoMachinesEvent();
9573 }
9574
9575 char *
9576 MakeName(char *template)
9577 {
9578     time_t clock;
9579     struct tm *tm;
9580     static char buf[MSG_SIZ];
9581     char *p = buf;
9582     int i;
9583
9584     clock = time((time_t *)NULL);
9585     tm = localtime(&clock);
9586
9587     while(*p++ = *template++) if(p[-1] == '%') {
9588         switch(*template++) {
9589           case 0:   *p = 0; return buf;
9590           case 'Y': i = tm->tm_year+1900; break;
9591           case 'y': i = tm->tm_year-100; break;
9592           case 'M': i = tm->tm_mon+1; break;
9593           case 'd': i = tm->tm_mday; break;
9594           case 'h': i = tm->tm_hour; break;
9595           case 'm': i = tm->tm_min; break;
9596           case 's': i = tm->tm_sec; break;
9597           default:  i = 0;
9598         }
9599         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9600     }
9601     return buf;
9602 }
9603
9604 int
9605 CountPlayers(char *p)
9606 {
9607     int n = 0;
9608     while(p = strchr(p, '\n')) p++, n++; // count participants
9609     return n;
9610 }
9611
9612 FILE *
9613 WriteTourneyFile(char *results)
9614 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9615     FILE *f = fopen(appData.tourneyFile, "w");
9616     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9617         // create a file with tournament description
9618         fprintf(f, "-participants {%s}\n", appData.participants);
9619         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9620         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9621         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9622         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9623         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9624         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9625         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9626         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9627         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9628         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9629         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9630         if(searchTime > 0)
9631                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9632         else {
9633                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9634                 fprintf(f, "-tc %s\n", appData.timeControl);
9635                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9636         }
9637         fprintf(f, "-results \"%s\"\n", results);
9638     }
9639     return f;
9640 }
9641
9642 int
9643 CreateTourney(char *name)
9644 {
9645         FILE *f;
9646         if(name[0] == NULLCHAR) {
9647             if(appData.participants[0])
9648                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9649             return 0;
9650         }
9651         f = fopen(name, "r");
9652         if(f) { // file exists
9653             ASSIGN(appData.tourneyFile, name);
9654             ParseArgsFromFile(f); // parse it
9655         } else {
9656             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9657             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9658                 DisplayError(_("Not enough participants"), 0);
9659                 return 0;
9660             }
9661             ASSIGN(appData.tourneyFile, name);
9662             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9663             if((f = WriteTourneyFile("")) == NULL) return 0;
9664         }
9665         fclose(f);
9666         appData.noChessProgram = FALSE;
9667         appData.clockMode = TRUE;
9668         SetGNUMode();
9669         return 1;
9670 }
9671
9672 #define MAXENGINES 1000
9673 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9674
9675 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9676 {
9677     char buf[MSG_SIZ], *p, *q;
9678     int i=1;
9679     while(*names) {
9680         p = names; q = buf;
9681         while(*p && *p != '\n') *q++ = *p++;
9682         *q = 0;
9683         if(engineList[i]) free(engineList[i]);
9684         engineList[i] = strdup(buf);
9685         if(*p == '\n') p++;
9686         TidyProgramName(engineList[i], "localhost", buf);
9687         if(engineMnemonic[i]) free(engineMnemonic[i]);
9688         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9689             strcat(buf, " (");
9690             sscanf(q + 8, "%s", buf + strlen(buf));
9691             strcat(buf, ")");
9692         }
9693         engineMnemonic[i] = strdup(buf);
9694         names = p; i++;
9695       if(i > MAXENGINES - 2) break;
9696     }
9697     engineList[i] = NULL;
9698 }
9699
9700 // following implemented as macro to avoid type limitations
9701 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9702
9703 void SwapEngines(int n)
9704 {   // swap settings for first engine and other engine (so far only some selected options)
9705     int h;
9706     char *p;
9707     if(n == 0) return;
9708     SWAP(directory, p)
9709     SWAP(chessProgram, p)
9710     SWAP(isUCI, h)
9711     SWAP(hasOwnBookUCI, h)
9712     SWAP(protocolVersion, h)
9713     SWAP(reuse, h)
9714     SWAP(scoreIsAbsolute, h)
9715     SWAP(timeOdds, h)
9716     SWAP(logo, p)
9717     SWAP(pgnName, p)
9718 }
9719
9720 void
9721 SetPlayer(int player)
9722 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9723     int i;
9724     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9725     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9726     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9727     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9728     if(mnemonic[i]) {
9729         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9730         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9731         ParseArgsFromString(buf);
9732     }
9733     free(engineName);
9734 }
9735
9736 int
9737 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9738 {   // determine players from game number
9739     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9740
9741     if(appData.tourneyType == 0) {
9742         roundsPerCycle = (nPlayers - 1) | 1;
9743         pairingsPerRound = nPlayers / 2;
9744     } else if(appData.tourneyType > 0) {
9745         roundsPerCycle = nPlayers - appData.tourneyType;
9746         pairingsPerRound = appData.tourneyType;
9747     }
9748     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9749     gamesPerCycle = gamesPerRound * roundsPerCycle;
9750     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9751     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9752     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9753     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9754     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9755     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9756
9757     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9758     if(appData.roundSync) *syncInterval = gamesPerRound;
9759
9760     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9761
9762     if(appData.tourneyType == 0) {
9763         if(curPairing == (nPlayers-1)/2 ) {
9764             *whitePlayer = curRound;
9765             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9766         } else {
9767             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9768             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9769             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9770             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9771         }
9772     } else if(appData.tourneyType > 0) {
9773         *whitePlayer = curPairing;
9774         *blackPlayer = curRound + appData.tourneyType;
9775     }
9776
9777     // take care of white/black alternation per round. 
9778     // For cycles and games this is already taken care of by default, derived from matchGame!
9779     return curRound & 1;
9780 }
9781
9782 int
9783 NextTourneyGame(int nr, int *swapColors)
9784 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9785     char *p, *q;
9786     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9787     FILE *tf;
9788     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9789     tf = fopen(appData.tourneyFile, "r");
9790     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9791     ParseArgsFromFile(tf); fclose(tf);
9792     InitTimeControls(); // TC might be altered from tourney file
9793
9794     nPlayers = CountPlayers(appData.participants); // count participants
9795     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9796     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9797
9798     if(syncInterval) {
9799         p = q = appData.results;
9800         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9801         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9802             DisplayMessage(_("Waiting for other game(s)"),"");
9803             waitingForGame = TRUE;
9804             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9805             return 0;
9806         }
9807         waitingForGame = FALSE;
9808     }
9809
9810     if(appData.tourneyType < 0) {
9811         if(nr>=0 && !pairingReceived) {
9812             char buf[1<<16];
9813             if(pairing.pr == NoProc) {
9814                 if(!appData.pairingEngine[0]) {
9815                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9816                     return 0;
9817                 }
9818                 StartChessProgram(&pairing); // starts the pairing engine
9819             }
9820             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9821             SendToProgram(buf, &pairing);
9822             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9823             SendToProgram(buf, &pairing);
9824             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9825         }
9826         pairingReceived = 0;                              // ... so we continue here 
9827         *swapColors = 0;
9828         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9829         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9830         matchGame = 1; roundNr = nr / syncInterval + 1;
9831     }
9832
9833     if(first.pr != NoProc) return 1; // engines already loaded
9834
9835     // redefine engines, engine dir, etc.
9836     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9837     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9838     SwapEngines(1);
9839     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9840     SwapEngines(1);         // and make that valid for second engine by swapping
9841     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9842     InitEngine(&second, 1);
9843     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9844     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9845     return 1;
9846 }
9847
9848 void
9849 NextMatchGame()
9850 {   // performs game initialization that does not invoke engines, and then tries to start the game
9851     int firstWhite, swapColors = 0;
9852     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9853     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9854     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9855     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9856     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9857     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9858     Reset(FALSE, first.pr != NoProc);
9859     appData.noChessProgram = FALSE;
9860     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9861     TwoMachinesEvent();
9862 }
9863
9864 void UserAdjudicationEvent( int result )
9865 {
9866     ChessMove gameResult = GameIsDrawn;
9867
9868     if( result > 0 ) {
9869         gameResult = WhiteWins;
9870     }
9871     else if( result < 0 ) {
9872         gameResult = BlackWins;
9873     }
9874
9875     if( gameMode == TwoMachinesPlay ) {
9876         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9877     }
9878 }
9879
9880
9881 // [HGM] save: calculate checksum of game to make games easily identifiable
9882 int StringCheckSum(char *s)
9883 {
9884         int i = 0;
9885         if(s==NULL) return 0;
9886         while(*s) i = i*259 + *s++;
9887         return i;
9888 }
9889
9890 int GameCheckSum()
9891 {
9892         int i, sum=0;
9893         for(i=backwardMostMove; i<forwardMostMove; i++) {
9894                 sum += pvInfoList[i].depth;
9895                 sum += StringCheckSum(parseList[i]);
9896                 sum += StringCheckSum(commentList[i]);
9897                 sum *= 261;
9898         }
9899         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9900         return sum + StringCheckSum(commentList[i]);
9901 } // end of save patch
9902
9903 void
9904 GameEnds(result, resultDetails, whosays)
9905      ChessMove result;
9906      char *resultDetails;
9907      int whosays;
9908 {
9909     GameMode nextGameMode;
9910     int isIcsGame;
9911     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9912
9913     if(endingGame) return; /* [HGM] crash: forbid recursion */
9914     endingGame = 1;
9915     if(twoBoards) { // [HGM] dual: switch back to one board
9916         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9917         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9918     }
9919     if (appData.debugMode) {
9920       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9921               result, resultDetails ? resultDetails : "(null)", whosays);
9922     }
9923
9924     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9925
9926     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9927         /* If we are playing on ICS, the server decides when the
9928            game is over, but the engine can offer to draw, claim
9929            a draw, or resign.
9930          */
9931 #if ZIPPY
9932         if (appData.zippyPlay && first.initDone) {
9933             if (result == GameIsDrawn) {
9934                 /* In case draw still needs to be claimed */
9935                 SendToICS(ics_prefix);
9936                 SendToICS("draw\n");
9937             } else if (StrCaseStr(resultDetails, "resign")) {
9938                 SendToICS(ics_prefix);
9939                 SendToICS("resign\n");
9940             }
9941         }
9942 #endif
9943         endingGame = 0; /* [HGM] crash */
9944         return;
9945     }
9946
9947     /* If we're loading the game from a file, stop */
9948     if (whosays == GE_FILE) {
9949       (void) StopLoadGameTimer();
9950       gameFileFP = NULL;
9951     }
9952
9953     /* Cancel draw offers */
9954     first.offeredDraw = second.offeredDraw = 0;
9955
9956     /* If this is an ICS game, only ICS can really say it's done;
9957        if not, anyone can. */
9958     isIcsGame = (gameMode == IcsPlayingWhite ||
9959                  gameMode == IcsPlayingBlack ||
9960                  gameMode == IcsObserving    ||
9961                  gameMode == IcsExamining);
9962
9963     if (!isIcsGame || whosays == GE_ICS) {
9964         /* OK -- not an ICS game, or ICS said it was done */
9965         StopClocks();
9966         if (!isIcsGame && !appData.noChessProgram)
9967           SetUserThinkingEnables();
9968
9969         /* [HGM] if a machine claims the game end we verify this claim */
9970         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9971             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9972                 char claimer;
9973                 ChessMove trueResult = (ChessMove) -1;
9974
9975                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9976                                             first.twoMachinesColor[0] :
9977                                             second.twoMachinesColor[0] ;
9978
9979                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9980                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9981                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9982                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9983                 } else
9984                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9985                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9986                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9987                 } else
9988                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9989                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9990                 }
9991
9992                 // now verify win claims, but not in drop games, as we don't understand those yet
9993                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9994                                                  || gameInfo.variant == VariantGreat) &&
9995                     (result == WhiteWins && claimer == 'w' ||
9996                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9997                       if (appData.debugMode) {
9998                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9999                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10000                       }
10001                       if(result != trueResult) {
10002                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10003                               result = claimer == 'w' ? BlackWins : WhiteWins;
10004                               resultDetails = buf;
10005                       }
10006                 } else
10007                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10008                     && (forwardMostMove <= backwardMostMove ||
10009                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10010                         (claimer=='b')==(forwardMostMove&1))
10011                                                                                   ) {
10012                       /* [HGM] verify: draws that were not flagged are false claims */
10013                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10014                       result = claimer == 'w' ? BlackWins : WhiteWins;
10015                       resultDetails = buf;
10016                 }
10017                 /* (Claiming a loss is accepted no questions asked!) */
10018             }
10019             /* [HGM] bare: don't allow bare King to win */
10020             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10021                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10022                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10023                && result != GameIsDrawn)
10024             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10025                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10026                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10027                         if(p >= 0 && p <= (int)WhiteKing) k++;
10028                 }
10029                 if (appData.debugMode) {
10030                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10031                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10032                 }
10033                 if(k <= 1) {
10034                         result = GameIsDrawn;
10035                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10036                         resultDetails = buf;
10037                 }
10038             }
10039         }
10040
10041
10042         if(serverMoves != NULL && !loadFlag) { char c = '=';
10043             if(result==WhiteWins) c = '+';
10044             if(result==BlackWins) c = '-';
10045             if(resultDetails != NULL)
10046                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10047         }
10048         if (resultDetails != NULL) {
10049             gameInfo.result = result;
10050             gameInfo.resultDetails = StrSave(resultDetails);
10051
10052             /* display last move only if game was not loaded from file */
10053             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10054                 DisplayMove(currentMove - 1);
10055
10056             if (forwardMostMove != 0) {
10057                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10058                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10059                                                                 ) {
10060                     if (*appData.saveGameFile != NULLCHAR) {
10061                         SaveGameToFile(appData.saveGameFile, TRUE);
10062                     } else if (appData.autoSaveGames) {
10063                         AutoSaveGame();
10064                     }
10065                     if (*appData.savePositionFile != NULLCHAR) {
10066                         SavePositionToFile(appData.savePositionFile);
10067                     }
10068                 }
10069             }
10070
10071             /* Tell program how game ended in case it is learning */
10072             /* [HGM] Moved this to after saving the PGN, just in case */
10073             /* engine died and we got here through time loss. In that */
10074             /* case we will get a fatal error writing the pipe, which */
10075             /* would otherwise lose us the PGN.                       */
10076             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10077             /* output during GameEnds should never be fatal anymore   */
10078             if (gameMode == MachinePlaysWhite ||
10079                 gameMode == MachinePlaysBlack ||
10080                 gameMode == TwoMachinesPlay ||
10081                 gameMode == IcsPlayingWhite ||
10082                 gameMode == IcsPlayingBlack ||
10083                 gameMode == BeginningOfGame) {
10084                 char buf[MSG_SIZ];
10085                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10086                         resultDetails);
10087                 if (first.pr != NoProc) {
10088                     SendToProgram(buf, &first);
10089                 }
10090                 if (second.pr != NoProc &&
10091                     gameMode == TwoMachinesPlay) {
10092                     SendToProgram(buf, &second);
10093                 }
10094             }
10095         }
10096
10097         if (appData.icsActive) {
10098             if (appData.quietPlay &&
10099                 (gameMode == IcsPlayingWhite ||
10100                  gameMode == IcsPlayingBlack)) {
10101                 SendToICS(ics_prefix);
10102                 SendToICS("set shout 1\n");
10103             }
10104             nextGameMode = IcsIdle;
10105             ics_user_moved = FALSE;
10106             /* clean up premove.  It's ugly when the game has ended and the
10107              * premove highlights are still on the board.
10108              */
10109             if (gotPremove) {
10110               gotPremove = FALSE;
10111               ClearPremoveHighlights();
10112               DrawPosition(FALSE, boards[currentMove]);
10113             }
10114             if (whosays == GE_ICS) {
10115                 switch (result) {
10116                 case WhiteWins:
10117                     if (gameMode == IcsPlayingWhite)
10118                         PlayIcsWinSound();
10119                     else if(gameMode == IcsPlayingBlack)
10120                         PlayIcsLossSound();
10121                     break;
10122                 case BlackWins:
10123                     if (gameMode == IcsPlayingBlack)
10124                         PlayIcsWinSound();
10125                     else if(gameMode == IcsPlayingWhite)
10126                         PlayIcsLossSound();
10127                     break;
10128                 case GameIsDrawn:
10129                     PlayIcsDrawSound();
10130                     break;
10131                 default:
10132                     PlayIcsUnfinishedSound();
10133                 }
10134             }
10135         } else if (gameMode == EditGame ||
10136                    gameMode == PlayFromGameFile ||
10137                    gameMode == AnalyzeMode ||
10138                    gameMode == AnalyzeFile) {
10139             nextGameMode = gameMode;
10140         } else {
10141             nextGameMode = EndOfGame;
10142         }
10143         pausing = FALSE;
10144         ModeHighlight();
10145     } else {
10146         nextGameMode = gameMode;
10147     }
10148
10149     if (appData.noChessProgram) {
10150         gameMode = nextGameMode;
10151         ModeHighlight();
10152         endingGame = 0; /* [HGM] crash */
10153         return;
10154     }
10155
10156     if (first.reuse) {
10157         /* Put first chess program into idle state */
10158         if (first.pr != NoProc &&
10159             (gameMode == MachinePlaysWhite ||
10160              gameMode == MachinePlaysBlack ||
10161              gameMode == TwoMachinesPlay ||
10162              gameMode == IcsPlayingWhite ||
10163              gameMode == IcsPlayingBlack ||
10164              gameMode == BeginningOfGame)) {
10165             SendToProgram("force\n", &first);
10166             if (first.usePing) {
10167               char buf[MSG_SIZ];
10168               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10169               SendToProgram(buf, &first);
10170             }
10171         }
10172     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10173         /* Kill off first chess program */
10174         if (first.isr != NULL)
10175           RemoveInputSource(first.isr);
10176         first.isr = NULL;
10177
10178         if (first.pr != NoProc) {
10179             ExitAnalyzeMode();
10180             DoSleep( appData.delayBeforeQuit );
10181             SendToProgram("quit\n", &first);
10182             DoSleep( appData.delayAfterQuit );
10183             DestroyChildProcess(first.pr, first.useSigterm);
10184         }
10185         first.pr = NoProc;
10186     }
10187     if (second.reuse) {
10188         /* Put second chess program into idle state */
10189         if (second.pr != NoProc &&
10190             gameMode == TwoMachinesPlay) {
10191             SendToProgram("force\n", &second);
10192             if (second.usePing) {
10193               char buf[MSG_SIZ];
10194               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10195               SendToProgram(buf, &second);
10196             }
10197         }
10198     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10199         /* Kill off second chess program */
10200         if (second.isr != NULL)
10201           RemoveInputSource(second.isr);
10202         second.isr = NULL;
10203
10204         if (second.pr != NoProc) {
10205             DoSleep( appData.delayBeforeQuit );
10206             SendToProgram("quit\n", &second);
10207             DoSleep( appData.delayAfterQuit );
10208             DestroyChildProcess(second.pr, second.useSigterm);
10209         }
10210         second.pr = NoProc;
10211     }
10212
10213     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10214         char resChar = '=';
10215         switch (result) {
10216         case WhiteWins:
10217           resChar = '+';
10218           if (first.twoMachinesColor[0] == 'w') {
10219             first.matchWins++;
10220           } else {
10221             second.matchWins++;
10222           }
10223           break;
10224         case BlackWins:
10225           resChar = '-';
10226           if (first.twoMachinesColor[0] == 'b') {
10227             first.matchWins++;
10228           } else {
10229             second.matchWins++;
10230           }
10231           break;
10232         case GameUnfinished:
10233           resChar = ' ';
10234         default:
10235           break;
10236         }
10237
10238         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10239         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10240             ReserveGame(nextGame, resChar); // sets nextGame
10241             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10242             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10243         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10244
10245         if (nextGame <= appData.matchGames && !abortMatch) {
10246             gameMode = nextGameMode;
10247             matchGame = nextGame; // this will be overruled in tourney mode!
10248             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10249             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10250             endingGame = 0; /* [HGM] crash */
10251             return;
10252         } else {
10253             gameMode = nextGameMode;
10254             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10255                      first.tidy, second.tidy,
10256                      first.matchWins, second.matchWins,
10257                      appData.matchGames - (first.matchWins + second.matchWins));
10258             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10259             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10260                 first.twoMachinesColor = "black\n";
10261                 second.twoMachinesColor = "white\n";
10262             } else {
10263                 first.twoMachinesColor = "white\n";
10264                 second.twoMachinesColor = "black\n";
10265             }
10266         }
10267     }
10268     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10269         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10270       ExitAnalyzeMode();
10271     gameMode = nextGameMode;
10272     ModeHighlight();
10273     endingGame = 0;  /* [HGM] crash */
10274     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10275         if(matchMode == TRUE) { // match through command line: exit with or without popup
10276             if(ranking) {
10277                 ToNrEvent(forwardMostMove);
10278                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10279                 else ExitEvent(0);
10280             } else DisplayFatalError(buf, 0, 0);
10281         } else { // match through menu; just stop, with or without popup
10282             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10283             ModeHighlight();
10284             if(ranking){
10285                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10286             } else DisplayNote(buf);
10287       }
10288       if(ranking) free(ranking);
10289     }
10290 }
10291
10292 /* Assumes program was just initialized (initString sent).
10293    Leaves program in force mode. */
10294 void
10295 FeedMovesToProgram(cps, upto)
10296      ChessProgramState *cps;
10297      int upto;
10298 {
10299     int i;
10300
10301     if (appData.debugMode)
10302       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10303               startedFromSetupPosition ? "position and " : "",
10304               backwardMostMove, upto, cps->which);
10305     if(currentlyInitializedVariant != gameInfo.variant) {
10306       char buf[MSG_SIZ];
10307         // [HGM] variantswitch: make engine aware of new variant
10308         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10309                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10310         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10311         SendToProgram(buf, cps);
10312         currentlyInitializedVariant = gameInfo.variant;
10313     }
10314     SendToProgram("force\n", cps);
10315     if (startedFromSetupPosition) {
10316         SendBoard(cps, backwardMostMove);
10317     if (appData.debugMode) {
10318         fprintf(debugFP, "feedMoves\n");
10319     }
10320     }
10321     for (i = backwardMostMove; i < upto; i++) {
10322         SendMoveToProgram(i, cps);
10323     }
10324 }
10325
10326
10327 int
10328 ResurrectChessProgram()
10329 {
10330      /* The chess program may have exited.
10331         If so, restart it and feed it all the moves made so far. */
10332     static int doInit = 0;
10333
10334     if (appData.noChessProgram) return 1;
10335
10336     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10337         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10338         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10339         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10340     } else {
10341         if (first.pr != NoProc) return 1;
10342         StartChessProgram(&first);
10343     }
10344     InitChessProgram(&first, FALSE);
10345     FeedMovesToProgram(&first, currentMove);
10346
10347     if (!first.sendTime) {
10348         /* can't tell gnuchess what its clock should read,
10349            so we bow to its notion. */
10350         ResetClocks();
10351         timeRemaining[0][currentMove] = whiteTimeRemaining;
10352         timeRemaining[1][currentMove] = blackTimeRemaining;
10353     }
10354
10355     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10356                 appData.icsEngineAnalyze) && first.analysisSupport) {
10357       SendToProgram("analyze\n", &first);
10358       first.analyzing = TRUE;
10359     }
10360     return 1;
10361 }
10362
10363 /*
10364  * Button procedures
10365  */
10366 void
10367 Reset(redraw, init)
10368      int redraw, init;
10369 {
10370     int i;
10371
10372     if (appData.debugMode) {
10373         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10374                 redraw, init, gameMode);
10375     }
10376     CleanupTail(); // [HGM] vari: delete any stored variations
10377     pausing = pauseExamInvalid = FALSE;
10378     startedFromSetupPosition = blackPlaysFirst = FALSE;
10379     firstMove = TRUE;
10380     whiteFlag = blackFlag = FALSE;
10381     userOfferedDraw = FALSE;
10382     hintRequested = bookRequested = FALSE;
10383     first.maybeThinking = FALSE;
10384     second.maybeThinking = FALSE;
10385     first.bookSuspend = FALSE; // [HGM] book
10386     second.bookSuspend = FALSE;
10387     thinkOutput[0] = NULLCHAR;
10388     lastHint[0] = NULLCHAR;
10389     ClearGameInfo(&gameInfo);
10390     gameInfo.variant = StringToVariant(appData.variant);
10391     ics_user_moved = ics_clock_paused = FALSE;
10392     ics_getting_history = H_FALSE;
10393     ics_gamenum = -1;
10394     white_holding[0] = black_holding[0] = NULLCHAR;
10395     ClearProgramStats();
10396     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10397
10398     ResetFrontEnd();
10399     ClearHighlights();
10400     flipView = appData.flipView;
10401     ClearPremoveHighlights();
10402     gotPremove = FALSE;
10403     alarmSounded = FALSE;
10404
10405     GameEnds(EndOfFile, NULL, GE_PLAYER);
10406     if(appData.serverMovesName != NULL) {
10407         /* [HGM] prepare to make moves file for broadcasting */
10408         clock_t t = clock();
10409         if(serverMoves != NULL) fclose(serverMoves);
10410         serverMoves = fopen(appData.serverMovesName, "r");
10411         if(serverMoves != NULL) {
10412             fclose(serverMoves);
10413             /* delay 15 sec before overwriting, so all clients can see end */
10414             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10415         }
10416         serverMoves = fopen(appData.serverMovesName, "w");
10417     }
10418
10419     ExitAnalyzeMode();
10420     gameMode = BeginningOfGame;
10421     ModeHighlight();
10422     if(appData.icsActive) gameInfo.variant = VariantNormal;
10423     currentMove = forwardMostMove = backwardMostMove = 0;
10424     InitPosition(redraw);
10425     for (i = 0; i < MAX_MOVES; i++) {
10426         if (commentList[i] != NULL) {
10427             free(commentList[i]);
10428             commentList[i] = NULL;
10429         }
10430     }
10431     ResetClocks();
10432     timeRemaining[0][0] = whiteTimeRemaining;
10433     timeRemaining[1][0] = blackTimeRemaining;
10434
10435     if (first.pr == NULL) {
10436         StartChessProgram(&first);
10437     }
10438     if (init) {
10439             InitChessProgram(&first, startedFromSetupPosition);
10440     }
10441     DisplayTitle("");
10442     DisplayMessage("", "");
10443     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10444     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10445 }
10446
10447 void
10448 AutoPlayGameLoop()
10449 {
10450     for (;;) {
10451         if (!AutoPlayOneMove())
10452           return;
10453         if (matchMode || appData.timeDelay == 0)
10454           continue;
10455         if (appData.timeDelay < 0)
10456           return;
10457         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10458         break;
10459     }
10460 }
10461
10462
10463 int
10464 AutoPlayOneMove()
10465 {
10466     int fromX, fromY, toX, toY;
10467
10468     if (appData.debugMode) {
10469       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10470     }
10471
10472     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10473       return FALSE;
10474
10475     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10476       pvInfoList[currentMove].depth = programStats.depth;
10477       pvInfoList[currentMove].score = programStats.score;
10478       pvInfoList[currentMove].time  = 0;
10479       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10480     }
10481
10482     if (currentMove >= forwardMostMove) {
10483       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10484       gameMode = EditGame;
10485       ModeHighlight();
10486
10487       /* [AS] Clear current move marker at the end of a game */
10488       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10489
10490       return FALSE;
10491     }
10492
10493     toX = moveList[currentMove][2] - AAA;
10494     toY = moveList[currentMove][3] - ONE;
10495
10496     if (moveList[currentMove][1] == '@') {
10497         if (appData.highlightLastMove) {
10498             SetHighlights(-1, -1, toX, toY);
10499         }
10500     } else {
10501         fromX = moveList[currentMove][0] - AAA;
10502         fromY = moveList[currentMove][1] - ONE;
10503
10504         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10505
10506         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10507
10508         if (appData.highlightLastMove) {
10509             SetHighlights(fromX, fromY, toX, toY);
10510         }
10511     }
10512     DisplayMove(currentMove);
10513     SendMoveToProgram(currentMove++, &first);
10514     DisplayBothClocks();
10515     DrawPosition(FALSE, boards[currentMove]);
10516     // [HGM] PV info: always display, routine tests if empty
10517     DisplayComment(currentMove - 1, commentList[currentMove]);
10518     return TRUE;
10519 }
10520
10521
10522 int
10523 LoadGameOneMove(readAhead)
10524      ChessMove readAhead;
10525 {
10526     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10527     char promoChar = NULLCHAR;
10528     ChessMove moveType;
10529     char move[MSG_SIZ];
10530     char *p, *q;
10531
10532     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10533         gameMode != AnalyzeMode && gameMode != Training) {
10534         gameFileFP = NULL;
10535         return FALSE;
10536     }
10537
10538     yyboardindex = forwardMostMove;
10539     if (readAhead != EndOfFile) {
10540       moveType = readAhead;
10541     } else {
10542       if (gameFileFP == NULL)
10543           return FALSE;
10544       moveType = (ChessMove) Myylex();
10545     }
10546
10547     done = FALSE;
10548     switch (moveType) {
10549       case Comment:
10550         if (appData.debugMode)
10551           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10552         p = yy_text;
10553
10554         /* append the comment but don't display it */
10555         AppendComment(currentMove, p, FALSE);
10556         return TRUE;
10557
10558       case WhiteCapturesEnPassant:
10559       case BlackCapturesEnPassant:
10560       case WhitePromotion:
10561       case BlackPromotion:
10562       case WhiteNonPromotion:
10563       case BlackNonPromotion:
10564       case NormalMove:
10565       case WhiteKingSideCastle:
10566       case WhiteQueenSideCastle:
10567       case BlackKingSideCastle:
10568       case BlackQueenSideCastle:
10569       case WhiteKingSideCastleWild:
10570       case WhiteQueenSideCastleWild:
10571       case BlackKingSideCastleWild:
10572       case BlackQueenSideCastleWild:
10573       /* PUSH Fabien */
10574       case WhiteHSideCastleFR:
10575       case WhiteASideCastleFR:
10576       case BlackHSideCastleFR:
10577       case BlackASideCastleFR:
10578       /* POP Fabien */
10579         if (appData.debugMode)
10580           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10581         fromX = currentMoveString[0] - AAA;
10582         fromY = currentMoveString[1] - ONE;
10583         toX = currentMoveString[2] - AAA;
10584         toY = currentMoveString[3] - ONE;
10585         promoChar = currentMoveString[4];
10586         break;
10587
10588       case WhiteDrop:
10589       case BlackDrop:
10590         if (appData.debugMode)
10591           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10592         fromX = moveType == WhiteDrop ?
10593           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10594         (int) CharToPiece(ToLower(currentMoveString[0]));
10595         fromY = DROP_RANK;
10596         toX = currentMoveString[2] - AAA;
10597         toY = currentMoveString[3] - ONE;
10598         break;
10599
10600       case WhiteWins:
10601       case BlackWins:
10602       case GameIsDrawn:
10603       case GameUnfinished:
10604         if (appData.debugMode)
10605           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10606         p = strchr(yy_text, '{');
10607         if (p == NULL) p = strchr(yy_text, '(');
10608         if (p == NULL) {
10609             p = yy_text;
10610             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10611         } else {
10612             q = strchr(p, *p == '{' ? '}' : ')');
10613             if (q != NULL) *q = NULLCHAR;
10614             p++;
10615         }
10616         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10617         GameEnds(moveType, p, GE_FILE);
10618         done = TRUE;
10619         if (cmailMsgLoaded) {
10620             ClearHighlights();
10621             flipView = WhiteOnMove(currentMove);
10622             if (moveType == GameUnfinished) flipView = !flipView;
10623             if (appData.debugMode)
10624               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10625         }
10626         break;
10627
10628       case EndOfFile:
10629         if (appData.debugMode)
10630           fprintf(debugFP, "Parser hit end of file\n");
10631         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10632           case MT_NONE:
10633           case MT_CHECK:
10634             break;
10635           case MT_CHECKMATE:
10636           case MT_STAINMATE:
10637             if (WhiteOnMove(currentMove)) {
10638                 GameEnds(BlackWins, "Black mates", GE_FILE);
10639             } else {
10640                 GameEnds(WhiteWins, "White mates", GE_FILE);
10641             }
10642             break;
10643           case MT_STALEMATE:
10644             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10645             break;
10646         }
10647         done = TRUE;
10648         break;
10649
10650       case MoveNumberOne:
10651         if (lastLoadGameStart == GNUChessGame) {
10652             /* GNUChessGames have numbers, but they aren't move numbers */
10653             if (appData.debugMode)
10654               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10655                       yy_text, (int) moveType);
10656             return LoadGameOneMove(EndOfFile); /* tail recursion */
10657         }
10658         /* else fall thru */
10659
10660       case XBoardGame:
10661       case GNUChessGame:
10662       case PGNTag:
10663         /* Reached start of next game in file */
10664         if (appData.debugMode)
10665           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10666         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10667           case MT_NONE:
10668           case MT_CHECK:
10669             break;
10670           case MT_CHECKMATE:
10671           case MT_STAINMATE:
10672             if (WhiteOnMove(currentMove)) {
10673                 GameEnds(BlackWins, "Black mates", GE_FILE);
10674             } else {
10675                 GameEnds(WhiteWins, "White mates", GE_FILE);
10676             }
10677             break;
10678           case MT_STALEMATE:
10679             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10680             break;
10681         }
10682         done = TRUE;
10683         break;
10684
10685       case PositionDiagram:     /* should not happen; ignore */
10686       case ElapsedTime:         /* ignore */
10687       case NAG:                 /* ignore */
10688         if (appData.debugMode)
10689           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10690                   yy_text, (int) moveType);
10691         return LoadGameOneMove(EndOfFile); /* tail recursion */
10692
10693       case IllegalMove:
10694         if (appData.testLegality) {
10695             if (appData.debugMode)
10696               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10697             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10698                     (forwardMostMove / 2) + 1,
10699                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10700             DisplayError(move, 0);
10701             done = TRUE;
10702         } else {
10703             if (appData.debugMode)
10704               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10705                       yy_text, currentMoveString);
10706             fromX = currentMoveString[0] - AAA;
10707             fromY = currentMoveString[1] - ONE;
10708             toX = currentMoveString[2] - AAA;
10709             toY = currentMoveString[3] - ONE;
10710             promoChar = currentMoveString[4];
10711         }
10712         break;
10713
10714       case AmbiguousMove:
10715         if (appData.debugMode)
10716           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10717         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10718                 (forwardMostMove / 2) + 1,
10719                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10720         DisplayError(move, 0);
10721         done = TRUE;
10722         break;
10723
10724       default:
10725       case ImpossibleMove:
10726         if (appData.debugMode)
10727           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10728         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10729                 (forwardMostMove / 2) + 1,
10730                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10731         DisplayError(move, 0);
10732         done = TRUE;
10733         break;
10734     }
10735
10736     if (done) {
10737         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10738             DrawPosition(FALSE, boards[currentMove]);
10739             DisplayBothClocks();
10740             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10741               DisplayComment(currentMove - 1, commentList[currentMove]);
10742         }
10743         (void) StopLoadGameTimer();
10744         gameFileFP = NULL;
10745         cmailOldMove = forwardMostMove;
10746         return FALSE;
10747     } else {
10748         /* currentMoveString is set as a side-effect of yylex */
10749
10750         thinkOutput[0] = NULLCHAR;
10751         MakeMove(fromX, fromY, toX, toY, promoChar);
10752         currentMove = forwardMostMove;
10753         return TRUE;
10754     }
10755 }
10756
10757 /* Load the nth game from the given file */
10758 int
10759 LoadGameFromFile(filename, n, title, useList)
10760      char *filename;
10761      int n;
10762      char *title;
10763      /*Boolean*/ int useList;
10764 {
10765     FILE *f;
10766     char buf[MSG_SIZ];
10767
10768     if (strcmp(filename, "-") == 0) {
10769         f = stdin;
10770         title = "stdin";
10771     } else {
10772         f = fopen(filename, "rb");
10773         if (f == NULL) {
10774           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10775             DisplayError(buf, errno);
10776             return FALSE;
10777         }
10778     }
10779     if (fseek(f, 0, 0) == -1) {
10780         /* f is not seekable; probably a pipe */
10781         useList = FALSE;
10782     }
10783     if (useList && n == 0) {
10784         int error = GameListBuild(f);
10785         if (error) {
10786             DisplayError(_("Cannot build game list"), error);
10787         } else if (!ListEmpty(&gameList) &&
10788                    ((ListGame *) gameList.tailPred)->number > 1) {
10789             GameListPopUp(f, title);
10790             return TRUE;
10791         }
10792         GameListDestroy();
10793         n = 1;
10794     }
10795     if (n == 0) n = 1;
10796     return LoadGame(f, n, title, FALSE);
10797 }
10798
10799
10800 void
10801 MakeRegisteredMove()
10802 {
10803     int fromX, fromY, toX, toY;
10804     char promoChar;
10805     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10806         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10807           case CMAIL_MOVE:
10808           case CMAIL_DRAW:
10809             if (appData.debugMode)
10810               fprintf(debugFP, "Restoring %s for game %d\n",
10811                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10812
10813             thinkOutput[0] = NULLCHAR;
10814             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10815             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10816             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10817             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10818             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10819             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10820             MakeMove(fromX, fromY, toX, toY, promoChar);
10821             ShowMove(fromX, fromY, toX, toY);
10822
10823             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10824               case MT_NONE:
10825               case MT_CHECK:
10826                 break;
10827
10828               case MT_CHECKMATE:
10829               case MT_STAINMATE:
10830                 if (WhiteOnMove(currentMove)) {
10831                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10832                 } else {
10833                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10834                 }
10835                 break;
10836
10837               case MT_STALEMATE:
10838                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10839                 break;
10840             }
10841
10842             break;
10843
10844           case CMAIL_RESIGN:
10845             if (WhiteOnMove(currentMove)) {
10846                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10847             } else {
10848                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10849             }
10850             break;
10851
10852           case CMAIL_ACCEPT:
10853             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10854             break;
10855
10856           default:
10857             break;
10858         }
10859     }
10860
10861     return;
10862 }
10863
10864 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10865 int
10866 CmailLoadGame(f, gameNumber, title, useList)
10867      FILE *f;
10868      int gameNumber;
10869      char *title;
10870      int useList;
10871 {
10872     int retVal;
10873
10874     if (gameNumber > nCmailGames) {
10875         DisplayError(_("No more games in this message"), 0);
10876         return FALSE;
10877     }
10878     if (f == lastLoadGameFP) {
10879         int offset = gameNumber - lastLoadGameNumber;
10880         if (offset == 0) {
10881             cmailMsg[0] = NULLCHAR;
10882             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10883                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10884                 nCmailMovesRegistered--;
10885             }
10886             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10887             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10888                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10889             }
10890         } else {
10891             if (! RegisterMove()) return FALSE;
10892         }
10893     }
10894
10895     retVal = LoadGame(f, gameNumber, title, useList);
10896
10897     /* Make move registered during previous look at this game, if any */
10898     MakeRegisteredMove();
10899
10900     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10901         commentList[currentMove]
10902           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10903         DisplayComment(currentMove - 1, commentList[currentMove]);
10904     }
10905
10906     return retVal;
10907 }
10908
10909 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10910 int
10911 ReloadGame(offset)
10912      int offset;
10913 {
10914     int gameNumber = lastLoadGameNumber + offset;
10915     if (lastLoadGameFP == NULL) {
10916         DisplayError(_("No game has been loaded yet"), 0);
10917         return FALSE;
10918     }
10919     if (gameNumber <= 0) {
10920         DisplayError(_("Can't back up any further"), 0);
10921         return FALSE;
10922     }
10923     if (cmailMsgLoaded) {
10924         return CmailLoadGame(lastLoadGameFP, gameNumber,
10925                              lastLoadGameTitle, lastLoadGameUseList);
10926     } else {
10927         return LoadGame(lastLoadGameFP, gameNumber,
10928                         lastLoadGameTitle, lastLoadGameUseList);
10929     }
10930 }
10931
10932
10933
10934 /* Load the nth game from open file f */
10935 int
10936 LoadGame(f, gameNumber, title, useList)
10937      FILE *f;
10938      int gameNumber;
10939      char *title;
10940      int useList;
10941 {
10942     ChessMove cm;
10943     char buf[MSG_SIZ];
10944     int gn = gameNumber;
10945     ListGame *lg = NULL;
10946     int numPGNTags = 0;
10947     int err;
10948     GameMode oldGameMode;
10949     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10950
10951     if (appData.debugMode)
10952         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10953
10954     if (gameMode == Training )
10955         SetTrainingModeOff();
10956
10957     oldGameMode = gameMode;
10958     if (gameMode != BeginningOfGame) {
10959       Reset(FALSE, TRUE);
10960     }
10961
10962     gameFileFP = f;
10963     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10964         fclose(lastLoadGameFP);
10965     }
10966
10967     if (useList) {
10968         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10969
10970         if (lg) {
10971             fseek(f, lg->offset, 0);
10972             GameListHighlight(gameNumber);
10973             gn = 1;
10974         }
10975         else {
10976             DisplayError(_("Game number out of range"), 0);
10977             return FALSE;
10978         }
10979     } else {
10980         GameListDestroy();
10981         if (fseek(f, 0, 0) == -1) {
10982             if (f == lastLoadGameFP ?
10983                 gameNumber == lastLoadGameNumber + 1 :
10984                 gameNumber == 1) {
10985                 gn = 1;
10986             } else {
10987                 DisplayError(_("Can't seek on game file"), 0);
10988                 return FALSE;
10989             }
10990         }
10991     }
10992     lastLoadGameFP = f;
10993     lastLoadGameNumber = gameNumber;
10994     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10995     lastLoadGameUseList = useList;
10996
10997     yynewfile(f);
10998
10999     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11000       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11001                 lg->gameInfo.black);
11002             DisplayTitle(buf);
11003     } else if (*title != NULLCHAR) {
11004         if (gameNumber > 1) {
11005           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11006             DisplayTitle(buf);
11007         } else {
11008             DisplayTitle(title);
11009         }
11010     }
11011
11012     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11013         gameMode = PlayFromGameFile;
11014         ModeHighlight();
11015     }
11016
11017     currentMove = forwardMostMove = backwardMostMove = 0;
11018     CopyBoard(boards[0], initialPosition);
11019     StopClocks();
11020
11021     /*
11022      * Skip the first gn-1 games in the file.
11023      * Also skip over anything that precedes an identifiable
11024      * start of game marker, to avoid being confused by
11025      * garbage at the start of the file.  Currently
11026      * recognized start of game markers are the move number "1",
11027      * the pattern "gnuchess .* game", the pattern
11028      * "^[#;%] [^ ]* game file", and a PGN tag block.
11029      * A game that starts with one of the latter two patterns
11030      * will also have a move number 1, possibly
11031      * following a position diagram.
11032      * 5-4-02: Let's try being more lenient and allowing a game to
11033      * start with an unnumbered move.  Does that break anything?
11034      */
11035     cm = lastLoadGameStart = EndOfFile;
11036     while (gn > 0) {
11037         yyboardindex = forwardMostMove;
11038         cm = (ChessMove) Myylex();
11039         switch (cm) {
11040           case EndOfFile:
11041             if (cmailMsgLoaded) {
11042                 nCmailGames = CMAIL_MAX_GAMES - gn;
11043             } else {
11044                 Reset(TRUE, TRUE);
11045                 DisplayError(_("Game not found in file"), 0);
11046             }
11047             return FALSE;
11048
11049           case GNUChessGame:
11050           case XBoardGame:
11051             gn--;
11052             lastLoadGameStart = cm;
11053             break;
11054
11055           case MoveNumberOne:
11056             switch (lastLoadGameStart) {
11057               case GNUChessGame:
11058               case XBoardGame:
11059               case PGNTag:
11060                 break;
11061               case MoveNumberOne:
11062               case EndOfFile:
11063                 gn--;           /* count this game */
11064                 lastLoadGameStart = cm;
11065                 break;
11066               default:
11067                 /* impossible */
11068                 break;
11069             }
11070             break;
11071
11072           case PGNTag:
11073             switch (lastLoadGameStart) {
11074               case GNUChessGame:
11075               case PGNTag:
11076               case MoveNumberOne:
11077               case EndOfFile:
11078                 gn--;           /* count this game */
11079                 lastLoadGameStart = cm;
11080                 break;
11081               case XBoardGame:
11082                 lastLoadGameStart = cm; /* game counted already */
11083                 break;
11084               default:
11085                 /* impossible */
11086                 break;
11087             }
11088             if (gn > 0) {
11089                 do {
11090                     yyboardindex = forwardMostMove;
11091                     cm = (ChessMove) Myylex();
11092                 } while (cm == PGNTag || cm == Comment);
11093             }
11094             break;
11095
11096           case WhiteWins:
11097           case BlackWins:
11098           case GameIsDrawn:
11099             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11100                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11101                     != CMAIL_OLD_RESULT) {
11102                     nCmailResults ++ ;
11103                     cmailResult[  CMAIL_MAX_GAMES
11104                                 - gn - 1] = CMAIL_OLD_RESULT;
11105                 }
11106             }
11107             break;
11108
11109           case NormalMove:
11110             /* Only a NormalMove can be at the start of a game
11111              * without a position diagram. */
11112             if (lastLoadGameStart == EndOfFile ) {
11113               gn--;
11114               lastLoadGameStart = MoveNumberOne;
11115             }
11116             break;
11117
11118           default:
11119             break;
11120         }
11121     }
11122
11123     if (appData.debugMode)
11124       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11125
11126     if (cm == XBoardGame) {
11127         /* Skip any header junk before position diagram and/or move 1 */
11128         for (;;) {
11129             yyboardindex = forwardMostMove;
11130             cm = (ChessMove) Myylex();
11131
11132             if (cm == EndOfFile ||
11133                 cm == GNUChessGame || cm == XBoardGame) {
11134                 /* Empty game; pretend end-of-file and handle later */
11135                 cm = EndOfFile;
11136                 break;
11137             }
11138
11139             if (cm == MoveNumberOne || cm == PositionDiagram ||
11140                 cm == PGNTag || cm == Comment)
11141               break;
11142         }
11143     } else if (cm == GNUChessGame) {
11144         if (gameInfo.event != NULL) {
11145             free(gameInfo.event);
11146         }
11147         gameInfo.event = StrSave(yy_text);
11148     }
11149
11150     startedFromSetupPosition = FALSE;
11151     while (cm == PGNTag) {
11152         if (appData.debugMode)
11153           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11154         err = ParsePGNTag(yy_text, &gameInfo);
11155         if (!err) numPGNTags++;
11156
11157         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11158         if(gameInfo.variant != oldVariant) {
11159             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11160             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11161             InitPosition(TRUE);
11162             oldVariant = gameInfo.variant;
11163             if (appData.debugMode)
11164               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11165         }
11166
11167
11168         if (gameInfo.fen != NULL) {
11169           Board initial_position;
11170           startedFromSetupPosition = TRUE;
11171           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11172             Reset(TRUE, TRUE);
11173             DisplayError(_("Bad FEN position in file"), 0);
11174             return FALSE;
11175           }
11176           CopyBoard(boards[0], initial_position);
11177           if (blackPlaysFirst) {
11178             currentMove = forwardMostMove = backwardMostMove = 1;
11179             CopyBoard(boards[1], initial_position);
11180             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11181             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11182             timeRemaining[0][1] = whiteTimeRemaining;
11183             timeRemaining[1][1] = blackTimeRemaining;
11184             if (commentList[0] != NULL) {
11185               commentList[1] = commentList[0];
11186               commentList[0] = NULL;
11187             }
11188           } else {
11189             currentMove = forwardMostMove = backwardMostMove = 0;
11190           }
11191           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11192           {   int i;
11193               initialRulePlies = FENrulePlies;
11194               for( i=0; i< nrCastlingRights; i++ )
11195                   initialRights[i] = initial_position[CASTLING][i];
11196           }
11197           yyboardindex = forwardMostMove;
11198           free(gameInfo.fen);
11199           gameInfo.fen = NULL;
11200         }
11201
11202         yyboardindex = forwardMostMove;
11203         cm = (ChessMove) Myylex();
11204
11205         /* Handle comments interspersed among the tags */
11206         while (cm == Comment) {
11207             char *p;
11208             if (appData.debugMode)
11209               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11210             p = yy_text;
11211             AppendComment(currentMove, p, FALSE);
11212             yyboardindex = forwardMostMove;
11213             cm = (ChessMove) Myylex();
11214         }
11215     }
11216
11217     /* don't rely on existence of Event tag since if game was
11218      * pasted from clipboard the Event tag may not exist
11219      */
11220     if (numPGNTags > 0){
11221         char *tags;
11222         if (gameInfo.variant == VariantNormal) {
11223           VariantClass v = StringToVariant(gameInfo.event);
11224           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11225           if(v < VariantShogi) gameInfo.variant = v;
11226         }
11227         if (!matchMode) {
11228           if( appData.autoDisplayTags ) {
11229             tags = PGNTags(&gameInfo);
11230             TagsPopUp(tags, CmailMsg());
11231             free(tags);
11232           }
11233         }
11234     } else {
11235         /* Make something up, but don't display it now */
11236         SetGameInfo();
11237         TagsPopDown();
11238     }
11239
11240     if (cm == PositionDiagram) {
11241         int i, j;
11242         char *p;
11243         Board initial_position;
11244
11245         if (appData.debugMode)
11246           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11247
11248         if (!startedFromSetupPosition) {
11249             p = yy_text;
11250             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11251               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11252                 switch (*p) {
11253                   case '{':
11254                   case '[':
11255                   case '-':
11256                   case ' ':
11257                   case '\t':
11258                   case '\n':
11259                   case '\r':
11260                     break;
11261                   default:
11262                     initial_position[i][j++] = CharToPiece(*p);
11263                     break;
11264                 }
11265             while (*p == ' ' || *p == '\t' ||
11266                    *p == '\n' || *p == '\r') p++;
11267
11268             if (strncmp(p, "black", strlen("black"))==0)
11269               blackPlaysFirst = TRUE;
11270             else
11271               blackPlaysFirst = FALSE;
11272             startedFromSetupPosition = TRUE;
11273
11274             CopyBoard(boards[0], initial_position);
11275             if (blackPlaysFirst) {
11276                 currentMove = forwardMostMove = backwardMostMove = 1;
11277                 CopyBoard(boards[1], initial_position);
11278                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11279                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11280                 timeRemaining[0][1] = whiteTimeRemaining;
11281                 timeRemaining[1][1] = blackTimeRemaining;
11282                 if (commentList[0] != NULL) {
11283                     commentList[1] = commentList[0];
11284                     commentList[0] = NULL;
11285                 }
11286             } else {
11287                 currentMove = forwardMostMove = backwardMostMove = 0;
11288             }
11289         }
11290         yyboardindex = forwardMostMove;
11291         cm = (ChessMove) Myylex();
11292     }
11293
11294     if (first.pr == NoProc) {
11295         StartChessProgram(&first);
11296     }
11297     InitChessProgram(&first, FALSE);
11298     SendToProgram("force\n", &first);
11299     if (startedFromSetupPosition) {
11300         SendBoard(&first, forwardMostMove);
11301     if (appData.debugMode) {
11302         fprintf(debugFP, "Load Game\n");
11303     }
11304         DisplayBothClocks();
11305     }
11306
11307     /* [HGM] server: flag to write setup moves in broadcast file as one */
11308     loadFlag = appData.suppressLoadMoves;
11309
11310     while (cm == Comment) {
11311         char *p;
11312         if (appData.debugMode)
11313           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11314         p = yy_text;
11315         AppendComment(currentMove, p, FALSE);
11316         yyboardindex = forwardMostMove;
11317         cm = (ChessMove) Myylex();
11318     }
11319
11320     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11321         cm == WhiteWins || cm == BlackWins ||
11322         cm == GameIsDrawn || cm == GameUnfinished) {
11323         DisplayMessage("", _("No moves in game"));
11324         if (cmailMsgLoaded) {
11325             if (appData.debugMode)
11326               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11327             ClearHighlights();
11328             flipView = FALSE;
11329         }
11330         DrawPosition(FALSE, boards[currentMove]);
11331         DisplayBothClocks();
11332         gameMode = EditGame;
11333         ModeHighlight();
11334         gameFileFP = NULL;
11335         cmailOldMove = 0;
11336         return TRUE;
11337     }
11338
11339     // [HGM] PV info: routine tests if comment empty
11340     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11341         DisplayComment(currentMove - 1, commentList[currentMove]);
11342     }
11343     if (!matchMode && appData.timeDelay != 0)
11344       DrawPosition(FALSE, boards[currentMove]);
11345
11346     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11347       programStats.ok_to_send = 1;
11348     }
11349
11350     /* if the first token after the PGN tags is a move
11351      * and not move number 1, retrieve it from the parser
11352      */
11353     if (cm != MoveNumberOne)
11354         LoadGameOneMove(cm);
11355
11356     /* load the remaining moves from the file */
11357     while (LoadGameOneMove(EndOfFile)) {
11358       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11359       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11360     }
11361
11362     /* rewind to the start of the game */
11363     currentMove = backwardMostMove;
11364
11365     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11366
11367     if (oldGameMode == AnalyzeFile ||
11368         oldGameMode == AnalyzeMode) {
11369       AnalyzeFileEvent();
11370     }
11371
11372     if (matchMode || appData.timeDelay == 0) {
11373       ToEndEvent();
11374       gameMode = EditGame;
11375       ModeHighlight();
11376     } else if (appData.timeDelay > 0) {
11377       AutoPlayGameLoop();
11378     }
11379
11380     if (appData.debugMode)
11381         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11382
11383     loadFlag = 0; /* [HGM] true game starts */
11384     return TRUE;
11385 }
11386
11387 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11388 int
11389 ReloadPosition(offset)
11390      int offset;
11391 {
11392     int positionNumber = lastLoadPositionNumber + offset;
11393     if (lastLoadPositionFP == NULL) {
11394         DisplayError(_("No position has been loaded yet"), 0);
11395         return FALSE;
11396     }
11397     if (positionNumber <= 0) {
11398         DisplayError(_("Can't back up any further"), 0);
11399         return FALSE;
11400     }
11401     return LoadPosition(lastLoadPositionFP, positionNumber,
11402                         lastLoadPositionTitle);
11403 }
11404
11405 /* Load the nth position from the given file */
11406 int
11407 LoadPositionFromFile(filename, n, title)
11408      char *filename;
11409      int n;
11410      char *title;
11411 {
11412     FILE *f;
11413     char buf[MSG_SIZ];
11414
11415     if (strcmp(filename, "-") == 0) {
11416         return LoadPosition(stdin, n, "stdin");
11417     } else {
11418         f = fopen(filename, "rb");
11419         if (f == NULL) {
11420             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11421             DisplayError(buf, errno);
11422             return FALSE;
11423         } else {
11424             return LoadPosition(f, n, title);
11425         }
11426     }
11427 }
11428
11429 /* Load the nth position from the given open file, and close it */
11430 int
11431 LoadPosition(f, positionNumber, title)
11432      FILE *f;
11433      int positionNumber;
11434      char *title;
11435 {
11436     char *p, line[MSG_SIZ];
11437     Board initial_position;
11438     int i, j, fenMode, pn;
11439
11440     if (gameMode == Training )
11441         SetTrainingModeOff();
11442
11443     if (gameMode != BeginningOfGame) {
11444         Reset(FALSE, TRUE);
11445     }
11446     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11447         fclose(lastLoadPositionFP);
11448     }
11449     if (positionNumber == 0) positionNumber = 1;
11450     lastLoadPositionFP = f;
11451     lastLoadPositionNumber = positionNumber;
11452     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11453     if (first.pr == NoProc) {
11454       StartChessProgram(&first);
11455       InitChessProgram(&first, FALSE);
11456     }
11457     pn = positionNumber;
11458     if (positionNumber < 0) {
11459         /* Negative position number means to seek to that byte offset */
11460         if (fseek(f, -positionNumber, 0) == -1) {
11461             DisplayError(_("Can't seek on position file"), 0);
11462             return FALSE;
11463         };
11464         pn = 1;
11465     } else {
11466         if (fseek(f, 0, 0) == -1) {
11467             if (f == lastLoadPositionFP ?
11468                 positionNumber == lastLoadPositionNumber + 1 :
11469                 positionNumber == 1) {
11470                 pn = 1;
11471             } else {
11472                 DisplayError(_("Can't seek on position file"), 0);
11473                 return FALSE;
11474             }
11475         }
11476     }
11477     /* See if this file is FEN or old-style xboard */
11478     if (fgets(line, MSG_SIZ, f) == NULL) {
11479         DisplayError(_("Position not found in file"), 0);
11480         return FALSE;
11481     }
11482     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11483     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11484
11485     if (pn >= 2) {
11486         if (fenMode || line[0] == '#') pn--;
11487         while (pn > 0) {
11488             /* skip positions before number pn */
11489             if (fgets(line, MSG_SIZ, f) == NULL) {
11490                 Reset(TRUE, TRUE);
11491                 DisplayError(_("Position not found in file"), 0);
11492                 return FALSE;
11493             }
11494             if (fenMode || line[0] == '#') pn--;
11495         }
11496     }
11497
11498     if (fenMode) {
11499         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11500             DisplayError(_("Bad FEN position in file"), 0);
11501             return FALSE;
11502         }
11503     } else {
11504         (void) fgets(line, MSG_SIZ, f);
11505         (void) fgets(line, MSG_SIZ, f);
11506
11507         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11508             (void) fgets(line, MSG_SIZ, f);
11509             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11510                 if (*p == ' ')
11511                   continue;
11512                 initial_position[i][j++] = CharToPiece(*p);
11513             }
11514         }
11515
11516         blackPlaysFirst = FALSE;
11517         if (!feof(f)) {
11518             (void) fgets(line, MSG_SIZ, f);
11519             if (strncmp(line, "black", strlen("black"))==0)
11520               blackPlaysFirst = TRUE;
11521         }
11522     }
11523     startedFromSetupPosition = TRUE;
11524
11525     SendToProgram("force\n", &first);
11526     CopyBoard(boards[0], initial_position);
11527     if (blackPlaysFirst) {
11528         currentMove = forwardMostMove = backwardMostMove = 1;
11529         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11530         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11531         CopyBoard(boards[1], initial_position);
11532         DisplayMessage("", _("Black to play"));
11533     } else {
11534         currentMove = forwardMostMove = backwardMostMove = 0;
11535         DisplayMessage("", _("White to play"));
11536     }
11537     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11538     SendBoard(&first, forwardMostMove);
11539     if (appData.debugMode) {
11540 int i, j;
11541   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11542   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11543         fprintf(debugFP, "Load Position\n");
11544     }
11545
11546     if (positionNumber > 1) {
11547       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11548         DisplayTitle(line);
11549     } else {
11550         DisplayTitle(title);
11551     }
11552     gameMode = EditGame;
11553     ModeHighlight();
11554     ResetClocks();
11555     timeRemaining[0][1] = whiteTimeRemaining;
11556     timeRemaining[1][1] = blackTimeRemaining;
11557     DrawPosition(FALSE, boards[currentMove]);
11558
11559     return TRUE;
11560 }
11561
11562
11563 void
11564 CopyPlayerNameIntoFileName(dest, src)
11565      char **dest, *src;
11566 {
11567     while (*src != NULLCHAR && *src != ',') {
11568         if (*src == ' ') {
11569             *(*dest)++ = '_';
11570             src++;
11571         } else {
11572             *(*dest)++ = *src++;
11573         }
11574     }
11575 }
11576
11577 char *DefaultFileName(ext)
11578      char *ext;
11579 {
11580     static char def[MSG_SIZ];
11581     char *p;
11582
11583     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11584         p = def;
11585         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11586         *p++ = '-';
11587         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11588         *p++ = '.';
11589         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11590     } else {
11591         def[0] = NULLCHAR;
11592     }
11593     return def;
11594 }
11595
11596 /* Save the current game to the given file */
11597 int
11598 SaveGameToFile(filename, append)
11599      char *filename;
11600      int append;
11601 {
11602     FILE *f;
11603     char buf[MSG_SIZ];
11604     int result;
11605
11606     if (strcmp(filename, "-") == 0) {
11607         return SaveGame(stdout, 0, NULL);
11608     } else {
11609         f = fopen(filename, append ? "a" : "w");
11610         if (f == NULL) {
11611             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11612             DisplayError(buf, errno);
11613             return FALSE;
11614         } else {
11615             safeStrCpy(buf, lastMsg, MSG_SIZ);
11616             DisplayMessage(_("Waiting for access to save file"), "");
11617             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11618             DisplayMessage(_("Saving game"), "");
11619             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11620             result = SaveGame(f, 0, NULL);
11621             DisplayMessage(buf, "");
11622             return result;
11623         }
11624     }
11625 }
11626
11627 char *
11628 SavePart(str)
11629      char *str;
11630 {
11631     static char buf[MSG_SIZ];
11632     char *p;
11633
11634     p = strchr(str, ' ');
11635     if (p == NULL) return str;
11636     strncpy(buf, str, p - str);
11637     buf[p - str] = NULLCHAR;
11638     return buf;
11639 }
11640
11641 #define PGN_MAX_LINE 75
11642
11643 #define PGN_SIDE_WHITE  0
11644 #define PGN_SIDE_BLACK  1
11645
11646 /* [AS] */
11647 static int FindFirstMoveOutOfBook( int side )
11648 {
11649     int result = -1;
11650
11651     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11652         int index = backwardMostMove;
11653         int has_book_hit = 0;
11654
11655         if( (index % 2) != side ) {
11656             index++;
11657         }
11658
11659         while( index < forwardMostMove ) {
11660             /* Check to see if engine is in book */
11661             int depth = pvInfoList[index].depth;
11662             int score = pvInfoList[index].score;
11663             int in_book = 0;
11664
11665             if( depth <= 2 ) {
11666                 in_book = 1;
11667             }
11668             else if( score == 0 && depth == 63 ) {
11669                 in_book = 1; /* Zappa */
11670             }
11671             else if( score == 2 && depth == 99 ) {
11672                 in_book = 1; /* Abrok */
11673             }
11674
11675             has_book_hit += in_book;
11676
11677             if( ! in_book ) {
11678                 result = index;
11679
11680                 break;
11681             }
11682
11683             index += 2;
11684         }
11685     }
11686
11687     return result;
11688 }
11689
11690 /* [AS] */
11691 void GetOutOfBookInfo( char * buf )
11692 {
11693     int oob[2];
11694     int i;
11695     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11696
11697     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11698     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11699
11700     *buf = '\0';
11701
11702     if( oob[0] >= 0 || oob[1] >= 0 ) {
11703         for( i=0; i<2; i++ ) {
11704             int idx = oob[i];
11705
11706             if( idx >= 0 ) {
11707                 if( i > 0 && oob[0] >= 0 ) {
11708                     strcat( buf, "   " );
11709                 }
11710
11711                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11712                 sprintf( buf+strlen(buf), "%s%.2f",
11713                     pvInfoList[idx].score >= 0 ? "+" : "",
11714                     pvInfoList[idx].score / 100.0 );
11715             }
11716         }
11717     }
11718 }
11719
11720 /* Save game in PGN style and close the file */
11721 int
11722 SaveGamePGN(f)
11723      FILE *f;
11724 {
11725     int i, offset, linelen, newblock;
11726     time_t tm;
11727 //    char *movetext;
11728     char numtext[32];
11729     int movelen, numlen, blank;
11730     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11731
11732     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11733
11734     tm = time((time_t *) NULL);
11735
11736     PrintPGNTags(f, &gameInfo);
11737
11738     if (backwardMostMove > 0 || startedFromSetupPosition) {
11739         char *fen = PositionToFEN(backwardMostMove, NULL);
11740         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11741         fprintf(f, "\n{--------------\n");
11742         PrintPosition(f, backwardMostMove);
11743         fprintf(f, "--------------}\n");
11744         free(fen);
11745     }
11746     else {
11747         /* [AS] Out of book annotation */
11748         if( appData.saveOutOfBookInfo ) {
11749             char buf[64];
11750
11751             GetOutOfBookInfo( buf );
11752
11753             if( buf[0] != '\0' ) {
11754                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11755             }
11756         }
11757
11758         fprintf(f, "\n");
11759     }
11760
11761     i = backwardMostMove;
11762     linelen = 0;
11763     newblock = TRUE;
11764
11765     while (i < forwardMostMove) {
11766         /* Print comments preceding this move */
11767         if (commentList[i] != NULL) {
11768             if (linelen > 0) fprintf(f, "\n");
11769             fprintf(f, "%s", commentList[i]);
11770             linelen = 0;
11771             newblock = TRUE;
11772         }
11773
11774         /* Format move number */
11775         if ((i % 2) == 0)
11776           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11777         else
11778           if (newblock)
11779             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11780           else
11781             numtext[0] = NULLCHAR;
11782
11783         numlen = strlen(numtext);
11784         newblock = FALSE;
11785
11786         /* Print move number */
11787         blank = linelen > 0 && numlen > 0;
11788         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11789             fprintf(f, "\n");
11790             linelen = 0;
11791             blank = 0;
11792         }
11793         if (blank) {
11794             fprintf(f, " ");
11795             linelen++;
11796         }
11797         fprintf(f, "%s", numtext);
11798         linelen += numlen;
11799
11800         /* Get move */
11801         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11802         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11803
11804         /* Print move */
11805         blank = linelen > 0 && movelen > 0;
11806         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11807             fprintf(f, "\n");
11808             linelen = 0;
11809             blank = 0;
11810         }
11811         if (blank) {
11812             fprintf(f, " ");
11813             linelen++;
11814         }
11815         fprintf(f, "%s", move_buffer);
11816         linelen += movelen;
11817
11818         /* [AS] Add PV info if present */
11819         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11820             /* [HGM] add time */
11821             char buf[MSG_SIZ]; int seconds;
11822
11823             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11824
11825             if( seconds <= 0)
11826               buf[0] = 0;
11827             else
11828               if( seconds < 30 )
11829                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11830               else
11831                 {
11832                   seconds = (seconds + 4)/10; // round to full seconds
11833                   if( seconds < 60 )
11834                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11835                   else
11836                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11837                 }
11838
11839             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11840                       pvInfoList[i].score >= 0 ? "+" : "",
11841                       pvInfoList[i].score / 100.0,
11842                       pvInfoList[i].depth,
11843                       buf );
11844
11845             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11846
11847             /* Print score/depth */
11848             blank = linelen > 0 && movelen > 0;
11849             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11850                 fprintf(f, "\n");
11851                 linelen = 0;
11852                 blank = 0;
11853             }
11854             if (blank) {
11855                 fprintf(f, " ");
11856                 linelen++;
11857             }
11858             fprintf(f, "%s", move_buffer);
11859             linelen += movelen;
11860         }
11861
11862         i++;
11863     }
11864
11865     /* Start a new line */
11866     if (linelen > 0) fprintf(f, "\n");
11867
11868     /* Print comments after last move */
11869     if (commentList[i] != NULL) {
11870         fprintf(f, "%s\n", commentList[i]);
11871     }
11872
11873     /* Print result */
11874     if (gameInfo.resultDetails != NULL &&
11875         gameInfo.resultDetails[0] != NULLCHAR) {
11876         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11877                 PGNResult(gameInfo.result));
11878     } else {
11879         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11880     }
11881
11882     fclose(f);
11883     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11884     return TRUE;
11885 }
11886
11887 /* Save game in old style and close the file */
11888 int
11889 SaveGameOldStyle(f)
11890      FILE *f;
11891 {
11892     int i, offset;
11893     time_t tm;
11894
11895     tm = time((time_t *) NULL);
11896
11897     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11898     PrintOpponents(f);
11899
11900     if (backwardMostMove > 0 || startedFromSetupPosition) {
11901         fprintf(f, "\n[--------------\n");
11902         PrintPosition(f, backwardMostMove);
11903         fprintf(f, "--------------]\n");
11904     } else {
11905         fprintf(f, "\n");
11906     }
11907
11908     i = backwardMostMove;
11909     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11910
11911     while (i < forwardMostMove) {
11912         if (commentList[i] != NULL) {
11913             fprintf(f, "[%s]\n", commentList[i]);
11914         }
11915
11916         if ((i % 2) == 1) {
11917             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11918             i++;
11919         } else {
11920             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11921             i++;
11922             if (commentList[i] != NULL) {
11923                 fprintf(f, "\n");
11924                 continue;
11925             }
11926             if (i >= forwardMostMove) {
11927                 fprintf(f, "\n");
11928                 break;
11929             }
11930             fprintf(f, "%s\n", parseList[i]);
11931             i++;
11932         }
11933     }
11934
11935     if (commentList[i] != NULL) {
11936         fprintf(f, "[%s]\n", commentList[i]);
11937     }
11938
11939     /* This isn't really the old style, but it's close enough */
11940     if (gameInfo.resultDetails != NULL &&
11941         gameInfo.resultDetails[0] != NULLCHAR) {
11942         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11943                 gameInfo.resultDetails);
11944     } else {
11945         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11946     }
11947
11948     fclose(f);
11949     return TRUE;
11950 }
11951
11952 /* Save the current game to open file f and close the file */
11953 int
11954 SaveGame(f, dummy, dummy2)
11955      FILE *f;
11956      int dummy;
11957      char *dummy2;
11958 {
11959     if (gameMode == EditPosition) EditPositionDone(TRUE);
11960     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11961     if (appData.oldSaveStyle)
11962       return SaveGameOldStyle(f);
11963     else
11964       return SaveGamePGN(f);
11965 }
11966
11967 /* Save the current position to the given file */
11968 int
11969 SavePositionToFile(filename)
11970      char *filename;
11971 {
11972     FILE *f;
11973     char buf[MSG_SIZ];
11974
11975     if (strcmp(filename, "-") == 0) {
11976         return SavePosition(stdout, 0, NULL);
11977     } else {
11978         f = fopen(filename, "a");
11979         if (f == NULL) {
11980             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11981             DisplayError(buf, errno);
11982             return FALSE;
11983         } else {
11984             safeStrCpy(buf, lastMsg, MSG_SIZ);
11985             DisplayMessage(_("Waiting for access to save file"), "");
11986             flock(fileno(f), LOCK_EX); // [HGM] lock
11987             DisplayMessage(_("Saving position"), "");
11988             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11989             SavePosition(f, 0, NULL);
11990             DisplayMessage(buf, "");
11991             return TRUE;
11992         }
11993     }
11994 }
11995
11996 /* Save the current position to the given open file and close the file */
11997 int
11998 SavePosition(f, dummy, dummy2)
11999      FILE *f;
12000      int dummy;
12001      char *dummy2;
12002 {
12003     time_t tm;
12004     char *fen;
12005
12006     if (gameMode == EditPosition) EditPositionDone(TRUE);
12007     if (appData.oldSaveStyle) {
12008         tm = time((time_t *) NULL);
12009
12010         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12011         PrintOpponents(f);
12012         fprintf(f, "[--------------\n");
12013         PrintPosition(f, currentMove);
12014         fprintf(f, "--------------]\n");
12015     } else {
12016         fen = PositionToFEN(currentMove, NULL);
12017         fprintf(f, "%s\n", fen);
12018         free(fen);
12019     }
12020     fclose(f);
12021     return TRUE;
12022 }
12023
12024 void
12025 ReloadCmailMsgEvent(unregister)
12026      int unregister;
12027 {
12028 #if !WIN32
12029     static char *inFilename = NULL;
12030     static char *outFilename;
12031     int i;
12032     struct stat inbuf, outbuf;
12033     int status;
12034
12035     /* Any registered moves are unregistered if unregister is set, */
12036     /* i.e. invoked by the signal handler */
12037     if (unregister) {
12038         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12039             cmailMoveRegistered[i] = FALSE;
12040             if (cmailCommentList[i] != NULL) {
12041                 free(cmailCommentList[i]);
12042                 cmailCommentList[i] = NULL;
12043             }
12044         }
12045         nCmailMovesRegistered = 0;
12046     }
12047
12048     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12049         cmailResult[i] = CMAIL_NOT_RESULT;
12050     }
12051     nCmailResults = 0;
12052
12053     if (inFilename == NULL) {
12054         /* Because the filenames are static they only get malloced once  */
12055         /* and they never get freed                                      */
12056         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12057         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12058
12059         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12060         sprintf(outFilename, "%s.out", appData.cmailGameName);
12061     }
12062
12063     status = stat(outFilename, &outbuf);
12064     if (status < 0) {
12065         cmailMailedMove = FALSE;
12066     } else {
12067         status = stat(inFilename, &inbuf);
12068         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12069     }
12070
12071     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12072        counts the games, notes how each one terminated, etc.
12073
12074        It would be nice to remove this kludge and instead gather all
12075        the information while building the game list.  (And to keep it
12076        in the game list nodes instead of having a bunch of fixed-size
12077        parallel arrays.)  Note this will require getting each game's
12078        termination from the PGN tags, as the game list builder does
12079        not process the game moves.  --mann
12080        */
12081     cmailMsgLoaded = TRUE;
12082     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12083
12084     /* Load first game in the file or popup game menu */
12085     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12086
12087 #endif /* !WIN32 */
12088     return;
12089 }
12090
12091 int
12092 RegisterMove()
12093 {
12094     FILE *f;
12095     char string[MSG_SIZ];
12096
12097     if (   cmailMailedMove
12098         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12099         return TRUE;            /* Allow free viewing  */
12100     }
12101
12102     /* Unregister move to ensure that we don't leave RegisterMove        */
12103     /* with the move registered when the conditions for registering no   */
12104     /* longer hold                                                       */
12105     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12106         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12107         nCmailMovesRegistered --;
12108
12109         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12110           {
12111               free(cmailCommentList[lastLoadGameNumber - 1]);
12112               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12113           }
12114     }
12115
12116     if (cmailOldMove == -1) {
12117         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12118         return FALSE;
12119     }
12120
12121     if (currentMove > cmailOldMove + 1) {
12122         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12123         return FALSE;
12124     }
12125
12126     if (currentMove < cmailOldMove) {
12127         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12128         return FALSE;
12129     }
12130
12131     if (forwardMostMove > currentMove) {
12132         /* Silently truncate extra moves */
12133         TruncateGame();
12134     }
12135
12136     if (   (currentMove == cmailOldMove + 1)
12137         || (   (currentMove == cmailOldMove)
12138             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12139                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12140         if (gameInfo.result != GameUnfinished) {
12141             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12142         }
12143
12144         if (commentList[currentMove] != NULL) {
12145             cmailCommentList[lastLoadGameNumber - 1]
12146               = StrSave(commentList[currentMove]);
12147         }
12148         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12149
12150         if (appData.debugMode)
12151           fprintf(debugFP, "Saving %s for game %d\n",
12152                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12153
12154         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12155
12156         f = fopen(string, "w");
12157         if (appData.oldSaveStyle) {
12158             SaveGameOldStyle(f); /* also closes the file */
12159
12160             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12161             f = fopen(string, "w");
12162             SavePosition(f, 0, NULL); /* also closes the file */
12163         } else {
12164             fprintf(f, "{--------------\n");
12165             PrintPosition(f, currentMove);
12166             fprintf(f, "--------------}\n\n");
12167
12168             SaveGame(f, 0, NULL); /* also closes the file*/
12169         }
12170
12171         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12172         nCmailMovesRegistered ++;
12173     } else if (nCmailGames == 1) {
12174         DisplayError(_("You have not made a move yet"), 0);
12175         return FALSE;
12176     }
12177
12178     return TRUE;
12179 }
12180
12181 void
12182 MailMoveEvent()
12183 {
12184 #if !WIN32
12185     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12186     FILE *commandOutput;
12187     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12188     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12189     int nBuffers;
12190     int i;
12191     int archived;
12192     char *arcDir;
12193
12194     if (! cmailMsgLoaded) {
12195         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12196         return;
12197     }
12198
12199     if (nCmailGames == nCmailResults) {
12200         DisplayError(_("No unfinished games"), 0);
12201         return;
12202     }
12203
12204 #if CMAIL_PROHIBIT_REMAIL
12205     if (cmailMailedMove) {
12206       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);
12207         DisplayError(msg, 0);
12208         return;
12209     }
12210 #endif
12211
12212     if (! (cmailMailedMove || RegisterMove())) return;
12213
12214     if (   cmailMailedMove
12215         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12216       snprintf(string, MSG_SIZ, partCommandString,
12217                appData.debugMode ? " -v" : "", appData.cmailGameName);
12218         commandOutput = popen(string, "r");
12219
12220         if (commandOutput == NULL) {
12221             DisplayError(_("Failed to invoke cmail"), 0);
12222         } else {
12223             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12224                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12225             }
12226             if (nBuffers > 1) {
12227                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12228                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12229                 nBytes = MSG_SIZ - 1;
12230             } else {
12231                 (void) memcpy(msg, buffer, nBytes);
12232             }
12233             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12234
12235             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12236                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12237
12238                 archived = TRUE;
12239                 for (i = 0; i < nCmailGames; i ++) {
12240                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12241                         archived = FALSE;
12242                     }
12243                 }
12244                 if (   archived
12245                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12246                         != NULL)) {
12247                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12248                            arcDir,
12249                            appData.cmailGameName,
12250                            gameInfo.date);
12251                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12252                     cmailMsgLoaded = FALSE;
12253                 }
12254             }
12255
12256             DisplayInformation(msg);
12257             pclose(commandOutput);
12258         }
12259     } else {
12260         if ((*cmailMsg) != '\0') {
12261             DisplayInformation(cmailMsg);
12262         }
12263     }
12264
12265     return;
12266 #endif /* !WIN32 */
12267 }
12268
12269 char *
12270 CmailMsg()
12271 {
12272 #if WIN32
12273     return NULL;
12274 #else
12275     int  prependComma = 0;
12276     char number[5];
12277     char string[MSG_SIZ];       /* Space for game-list */
12278     int  i;
12279
12280     if (!cmailMsgLoaded) return "";
12281
12282     if (cmailMailedMove) {
12283       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12284     } else {
12285         /* Create a list of games left */
12286       snprintf(string, MSG_SIZ, "[");
12287         for (i = 0; i < nCmailGames; i ++) {
12288             if (! (   cmailMoveRegistered[i]
12289                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12290                 if (prependComma) {
12291                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12292                 } else {
12293                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12294                     prependComma = 1;
12295                 }
12296
12297                 strcat(string, number);
12298             }
12299         }
12300         strcat(string, "]");
12301
12302         if (nCmailMovesRegistered + nCmailResults == 0) {
12303             switch (nCmailGames) {
12304               case 1:
12305                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12306                 break;
12307
12308               case 2:
12309                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12310                 break;
12311
12312               default:
12313                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12314                          nCmailGames);
12315                 break;
12316             }
12317         } else {
12318             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12319               case 1:
12320                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12321                          string);
12322                 break;
12323
12324               case 0:
12325                 if (nCmailResults == nCmailGames) {
12326                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12327                 } else {
12328                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12329                 }
12330                 break;
12331
12332               default:
12333                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12334                          string);
12335             }
12336         }
12337     }
12338     return cmailMsg;
12339 #endif /* WIN32 */
12340 }
12341
12342 void
12343 ResetGameEvent()
12344 {
12345     if (gameMode == Training)
12346       SetTrainingModeOff();
12347
12348     Reset(TRUE, TRUE);
12349     cmailMsgLoaded = FALSE;
12350     if (appData.icsActive) {
12351       SendToICS(ics_prefix);
12352       SendToICS("refresh\n");
12353     }
12354 }
12355
12356 void
12357 ExitEvent(status)
12358      int status;
12359 {
12360     exiting++;
12361     if (exiting > 2) {
12362       /* Give up on clean exit */
12363       exit(status);
12364     }
12365     if (exiting > 1) {
12366       /* Keep trying for clean exit */
12367       return;
12368     }
12369
12370     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12371
12372     if (telnetISR != NULL) {
12373       RemoveInputSource(telnetISR);
12374     }
12375     if (icsPR != NoProc) {
12376       DestroyChildProcess(icsPR, TRUE);
12377     }
12378
12379     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12380     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12381
12382     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12383     /* make sure this other one finishes before killing it!                  */
12384     if(endingGame) { int count = 0;
12385         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12386         while(endingGame && count++ < 10) DoSleep(1);
12387         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12388     }
12389
12390     /* Kill off chess programs */
12391     if (first.pr != NoProc) {
12392         ExitAnalyzeMode();
12393
12394         DoSleep( appData.delayBeforeQuit );
12395         SendToProgram("quit\n", &first);
12396         DoSleep( appData.delayAfterQuit );
12397         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12398     }
12399     if (second.pr != NoProc) {
12400         DoSleep( appData.delayBeforeQuit );
12401         SendToProgram("quit\n", &second);
12402         DoSleep( appData.delayAfterQuit );
12403         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12404     }
12405     if (first.isr != NULL) {
12406         RemoveInputSource(first.isr);
12407     }
12408     if (second.isr != NULL) {
12409         RemoveInputSource(second.isr);
12410     }
12411
12412     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12413     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12414
12415     ShutDownFrontEnd();
12416     exit(status);
12417 }
12418
12419 void
12420 PauseEvent()
12421 {
12422     if (appData.debugMode)
12423         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12424     if (pausing) {
12425         pausing = FALSE;
12426         ModeHighlight();
12427         if (gameMode == MachinePlaysWhite ||
12428             gameMode == MachinePlaysBlack) {
12429             StartClocks();
12430         } else {
12431             DisplayBothClocks();
12432         }
12433         if (gameMode == PlayFromGameFile) {
12434             if (appData.timeDelay >= 0)
12435                 AutoPlayGameLoop();
12436         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12437             Reset(FALSE, TRUE);
12438             SendToICS(ics_prefix);
12439             SendToICS("refresh\n");
12440         } else if (currentMove < forwardMostMove) {
12441             ForwardInner(forwardMostMove);
12442         }
12443         pauseExamInvalid = FALSE;
12444     } else {
12445         switch (gameMode) {
12446           default:
12447             return;
12448           case IcsExamining:
12449             pauseExamForwardMostMove = forwardMostMove;
12450             pauseExamInvalid = FALSE;
12451             /* fall through */
12452           case IcsObserving:
12453           case IcsPlayingWhite:
12454           case IcsPlayingBlack:
12455             pausing = TRUE;
12456             ModeHighlight();
12457             return;
12458           case PlayFromGameFile:
12459             (void) StopLoadGameTimer();
12460             pausing = TRUE;
12461             ModeHighlight();
12462             break;
12463           case BeginningOfGame:
12464             if (appData.icsActive) return;
12465             /* else fall through */
12466           case MachinePlaysWhite:
12467           case MachinePlaysBlack:
12468           case TwoMachinesPlay:
12469             if (forwardMostMove == 0)
12470               return;           /* don't pause if no one has moved */
12471             if ((gameMode == MachinePlaysWhite &&
12472                  !WhiteOnMove(forwardMostMove)) ||
12473                 (gameMode == MachinePlaysBlack &&
12474                  WhiteOnMove(forwardMostMove))) {
12475                 StopClocks();
12476             }
12477             pausing = TRUE;
12478             ModeHighlight();
12479             break;
12480         }
12481     }
12482 }
12483
12484 void
12485 EditCommentEvent()
12486 {
12487     char title[MSG_SIZ];
12488
12489     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12490       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12491     } else {
12492       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12493                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12494                parseList[currentMove - 1]);
12495     }
12496
12497     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12498 }
12499
12500
12501 void
12502 EditTagsEvent()
12503 {
12504     char *tags = PGNTags(&gameInfo);
12505     bookUp = FALSE;
12506     EditTagsPopUp(tags, NULL);
12507     free(tags);
12508 }
12509
12510 void
12511 AnalyzeModeEvent()
12512 {
12513     if (appData.noChessProgram || gameMode == AnalyzeMode)
12514       return;
12515
12516     if (gameMode != AnalyzeFile) {
12517         if (!appData.icsEngineAnalyze) {
12518                EditGameEvent();
12519                if (gameMode != EditGame) return;
12520         }
12521         ResurrectChessProgram();
12522         SendToProgram("analyze\n", &first);
12523         first.analyzing = TRUE;
12524         /*first.maybeThinking = TRUE;*/
12525         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12526         EngineOutputPopUp();
12527     }
12528     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12529     pausing = FALSE;
12530     ModeHighlight();
12531     SetGameInfo();
12532
12533     StartAnalysisClock();
12534     GetTimeMark(&lastNodeCountTime);
12535     lastNodeCount = 0;
12536 }
12537
12538 void
12539 AnalyzeFileEvent()
12540 {
12541     if (appData.noChessProgram || gameMode == AnalyzeFile)
12542       return;
12543
12544     if (gameMode != AnalyzeMode) {
12545         EditGameEvent();
12546         if (gameMode != EditGame) return;
12547         ResurrectChessProgram();
12548         SendToProgram("analyze\n", &first);
12549         first.analyzing = TRUE;
12550         /*first.maybeThinking = TRUE;*/
12551         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12552         EngineOutputPopUp();
12553     }
12554     gameMode = AnalyzeFile;
12555     pausing = FALSE;
12556     ModeHighlight();
12557     SetGameInfo();
12558
12559     StartAnalysisClock();
12560     GetTimeMark(&lastNodeCountTime);
12561     lastNodeCount = 0;
12562 }
12563
12564 void
12565 MachineWhiteEvent()
12566 {
12567     char buf[MSG_SIZ];
12568     char *bookHit = NULL;
12569
12570     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12571       return;
12572
12573
12574     if (gameMode == PlayFromGameFile ||
12575         gameMode == TwoMachinesPlay  ||
12576         gameMode == Training         ||
12577         gameMode == AnalyzeMode      ||
12578         gameMode == EndOfGame)
12579         EditGameEvent();
12580
12581     if (gameMode == EditPosition)
12582         EditPositionDone(TRUE);
12583
12584     if (!WhiteOnMove(currentMove)) {
12585         DisplayError(_("It is not White's turn"), 0);
12586         return;
12587     }
12588
12589     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12590       ExitAnalyzeMode();
12591
12592     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12593         gameMode == AnalyzeFile)
12594         TruncateGame();
12595
12596     ResurrectChessProgram();    /* in case it isn't running */
12597     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12598         gameMode = MachinePlaysWhite;
12599         ResetClocks();
12600     } else
12601     gameMode = MachinePlaysWhite;
12602     pausing = FALSE;
12603     ModeHighlight();
12604     SetGameInfo();
12605     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12606     DisplayTitle(buf);
12607     if (first.sendName) {
12608       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12609       SendToProgram(buf, &first);
12610     }
12611     if (first.sendTime) {
12612       if (first.useColors) {
12613         SendToProgram("black\n", &first); /*gnu kludge*/
12614       }
12615       SendTimeRemaining(&first, TRUE);
12616     }
12617     if (first.useColors) {
12618       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12619     }
12620     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12621     SetMachineThinkingEnables();
12622     first.maybeThinking = TRUE;
12623     StartClocks();
12624     firstMove = FALSE;
12625
12626     if (appData.autoFlipView && !flipView) {
12627       flipView = !flipView;
12628       DrawPosition(FALSE, NULL);
12629       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12630     }
12631
12632     if(bookHit) { // [HGM] book: simulate book reply
12633         static char bookMove[MSG_SIZ]; // a bit generous?
12634
12635         programStats.nodes = programStats.depth = programStats.time =
12636         programStats.score = programStats.got_only_move = 0;
12637         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12638
12639         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12640         strcat(bookMove, bookHit);
12641         HandleMachineMove(bookMove, &first);
12642     }
12643 }
12644
12645 void
12646 MachineBlackEvent()
12647 {
12648   char buf[MSG_SIZ];
12649   char *bookHit = NULL;
12650
12651     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12652         return;
12653
12654
12655     if (gameMode == PlayFromGameFile ||
12656         gameMode == TwoMachinesPlay  ||
12657         gameMode == Training         ||
12658         gameMode == AnalyzeMode      ||
12659         gameMode == EndOfGame)
12660         EditGameEvent();
12661
12662     if (gameMode == EditPosition)
12663         EditPositionDone(TRUE);
12664
12665     if (WhiteOnMove(currentMove)) {
12666         DisplayError(_("It is not Black's turn"), 0);
12667         return;
12668     }
12669
12670     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12671       ExitAnalyzeMode();
12672
12673     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12674         gameMode == AnalyzeFile)
12675         TruncateGame();
12676
12677     ResurrectChessProgram();    /* in case it isn't running */
12678     gameMode = MachinePlaysBlack;
12679     pausing = FALSE;
12680     ModeHighlight();
12681     SetGameInfo();
12682     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12683     DisplayTitle(buf);
12684     if (first.sendName) {
12685       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12686       SendToProgram(buf, &first);
12687     }
12688     if (first.sendTime) {
12689       if (first.useColors) {
12690         SendToProgram("white\n", &first); /*gnu kludge*/
12691       }
12692       SendTimeRemaining(&first, FALSE);
12693     }
12694     if (first.useColors) {
12695       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12696     }
12697     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12698     SetMachineThinkingEnables();
12699     first.maybeThinking = TRUE;
12700     StartClocks();
12701
12702     if (appData.autoFlipView && flipView) {
12703       flipView = !flipView;
12704       DrawPosition(FALSE, NULL);
12705       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12706     }
12707     if(bookHit) { // [HGM] book: simulate book reply
12708         static char bookMove[MSG_SIZ]; // a bit generous?
12709
12710         programStats.nodes = programStats.depth = programStats.time =
12711         programStats.score = programStats.got_only_move = 0;
12712         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12713
12714         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12715         strcat(bookMove, bookHit);
12716         HandleMachineMove(bookMove, &first);
12717     }
12718 }
12719
12720
12721 void
12722 DisplayTwoMachinesTitle()
12723 {
12724     char buf[MSG_SIZ];
12725     if (appData.matchGames > 0) {
12726         if(appData.tourneyFile[0]) {
12727           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12728                    gameInfo.white, gameInfo.black,
12729                    nextGame+1, appData.matchGames+1,
12730                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12731         } else 
12732         if (first.twoMachinesColor[0] == 'w') {
12733           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12734                    gameInfo.white, gameInfo.black,
12735                    first.matchWins, second.matchWins,
12736                    matchGame - 1 - (first.matchWins + second.matchWins));
12737         } else {
12738           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12739                    gameInfo.white, gameInfo.black,
12740                    second.matchWins, first.matchWins,
12741                    matchGame - 1 - (first.matchWins + second.matchWins));
12742         }
12743     } else {
12744       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12745     }
12746     DisplayTitle(buf);
12747 }
12748
12749 void
12750 SettingsMenuIfReady()
12751 {
12752   if (second.lastPing != second.lastPong) {
12753     DisplayMessage("", _("Waiting for second chess program"));
12754     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12755     return;
12756   }
12757   ThawUI();
12758   DisplayMessage("", "");
12759   SettingsPopUp(&second);
12760 }
12761
12762 int
12763 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12764 {
12765     char buf[MSG_SIZ];
12766     if (cps->pr == NULL) {
12767         StartChessProgram(cps);
12768         if (cps->protocolVersion == 1) {
12769           retry();
12770         } else {
12771           /* kludge: allow timeout for initial "feature" command */
12772           FreezeUI();
12773           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12774           DisplayMessage("", buf);
12775           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12776         }
12777         return 1;
12778     }
12779     return 0;
12780 }
12781
12782 void
12783 TwoMachinesEvent P((void))
12784 {
12785     int i;
12786     char buf[MSG_SIZ];
12787     ChessProgramState *onmove;
12788     char *bookHit = NULL;
12789     static int stalling = 0;
12790     TimeMark now;
12791     long wait;
12792
12793     if (appData.noChessProgram) return;
12794
12795     switch (gameMode) {
12796       case TwoMachinesPlay:
12797         return;
12798       case MachinePlaysWhite:
12799       case MachinePlaysBlack:
12800         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12801             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12802             return;
12803         }
12804         /* fall through */
12805       case BeginningOfGame:
12806       case PlayFromGameFile:
12807       case EndOfGame:
12808         EditGameEvent();
12809         if (gameMode != EditGame) return;
12810         break;
12811       case EditPosition:
12812         EditPositionDone(TRUE);
12813         break;
12814       case AnalyzeMode:
12815       case AnalyzeFile:
12816         ExitAnalyzeMode();
12817         break;
12818       case EditGame:
12819       default:
12820         break;
12821     }
12822
12823 //    forwardMostMove = currentMove;
12824     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12825
12826     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12827
12828     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12829     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12830       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12831       return;
12832     }
12833     if(!stalling) {
12834       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12835       SendToProgram("force\n", &second);
12836       stalling = 1;
12837       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12838       return;
12839     }
12840     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12841     if(appData.matchPause>10000 || appData.matchPause<10)
12842                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12843     wait = SubtractTimeMarks(&now, &pauseStart);
12844     if(wait < appData.matchPause) {
12845         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12846         return;
12847     }
12848     stalling = 0;
12849     DisplayMessage("", "");
12850     if (startedFromSetupPosition) {
12851         SendBoard(&second, backwardMostMove);
12852     if (appData.debugMode) {
12853         fprintf(debugFP, "Two Machines\n");
12854     }
12855     }
12856     for (i = backwardMostMove; i < forwardMostMove; i++) {
12857         SendMoveToProgram(i, &second);
12858     }
12859
12860     gameMode = TwoMachinesPlay;
12861     pausing = FALSE;
12862     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12863     SetGameInfo();
12864     DisplayTwoMachinesTitle();
12865     firstMove = TRUE;
12866     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12867         onmove = &first;
12868     } else {
12869         onmove = &second;
12870     }
12871     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12872     SendToProgram(first.computerString, &first);
12873     if (first.sendName) {
12874       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12875       SendToProgram(buf, &first);
12876     }
12877     SendToProgram(second.computerString, &second);
12878     if (second.sendName) {
12879       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12880       SendToProgram(buf, &second);
12881     }
12882
12883     ResetClocks();
12884     if (!first.sendTime || !second.sendTime) {
12885         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12886         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12887     }
12888     if (onmove->sendTime) {
12889       if (onmove->useColors) {
12890         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12891       }
12892       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12893     }
12894     if (onmove->useColors) {
12895       SendToProgram(onmove->twoMachinesColor, onmove);
12896     }
12897     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12898 //    SendToProgram("go\n", onmove);
12899     onmove->maybeThinking = TRUE;
12900     SetMachineThinkingEnables();
12901
12902     StartClocks();
12903
12904     if(bookHit) { // [HGM] book: simulate book reply
12905         static char bookMove[MSG_SIZ]; // a bit generous?
12906
12907         programStats.nodes = programStats.depth = programStats.time =
12908         programStats.score = programStats.got_only_move = 0;
12909         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12910
12911         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12912         strcat(bookMove, bookHit);
12913         savedMessage = bookMove; // args for deferred call
12914         savedState = onmove;
12915         ScheduleDelayedEvent(DeferredBookMove, 1);
12916     }
12917 }
12918
12919 void
12920 TrainingEvent()
12921 {
12922     if (gameMode == Training) {
12923       SetTrainingModeOff();
12924       gameMode = PlayFromGameFile;
12925       DisplayMessage("", _("Training mode off"));
12926     } else {
12927       gameMode = Training;
12928       animateTraining = appData.animate;
12929
12930       /* make sure we are not already at the end of the game */
12931       if (currentMove < forwardMostMove) {
12932         SetTrainingModeOn();
12933         DisplayMessage("", _("Training mode on"));
12934       } else {
12935         gameMode = PlayFromGameFile;
12936         DisplayError(_("Already at end of game"), 0);
12937       }
12938     }
12939     ModeHighlight();
12940 }
12941
12942 void
12943 IcsClientEvent()
12944 {
12945     if (!appData.icsActive) return;
12946     switch (gameMode) {
12947       case IcsPlayingWhite:
12948       case IcsPlayingBlack:
12949       case IcsObserving:
12950       case IcsIdle:
12951       case BeginningOfGame:
12952       case IcsExamining:
12953         return;
12954
12955       case EditGame:
12956         break;
12957
12958       case EditPosition:
12959         EditPositionDone(TRUE);
12960         break;
12961
12962       case AnalyzeMode:
12963       case AnalyzeFile:
12964         ExitAnalyzeMode();
12965         break;
12966
12967       default:
12968         EditGameEvent();
12969         break;
12970     }
12971
12972     gameMode = IcsIdle;
12973     ModeHighlight();
12974     return;
12975 }
12976
12977
12978 void
12979 EditGameEvent()
12980 {
12981     int i;
12982
12983     switch (gameMode) {
12984       case Training:
12985         SetTrainingModeOff();
12986         break;
12987       case MachinePlaysWhite:
12988       case MachinePlaysBlack:
12989       case BeginningOfGame:
12990         SendToProgram("force\n", &first);
12991         SetUserThinkingEnables();
12992         break;
12993       case PlayFromGameFile:
12994         (void) StopLoadGameTimer();
12995         if (gameFileFP != NULL) {
12996             gameFileFP = NULL;
12997         }
12998         break;
12999       case EditPosition:
13000         EditPositionDone(TRUE);
13001         break;
13002       case AnalyzeMode:
13003       case AnalyzeFile:
13004         ExitAnalyzeMode();
13005         SendToProgram("force\n", &first);
13006         break;
13007       case TwoMachinesPlay:
13008         GameEnds(EndOfFile, NULL, GE_PLAYER);
13009         ResurrectChessProgram();
13010         SetUserThinkingEnables();
13011         break;
13012       case EndOfGame:
13013         ResurrectChessProgram();
13014         break;
13015       case IcsPlayingBlack:
13016       case IcsPlayingWhite:
13017         DisplayError(_("Warning: You are still playing a game"), 0);
13018         break;
13019       case IcsObserving:
13020         DisplayError(_("Warning: You are still observing a game"), 0);
13021         break;
13022       case IcsExamining:
13023         DisplayError(_("Warning: You are still examining a game"), 0);
13024         break;
13025       case IcsIdle:
13026         break;
13027       case EditGame:
13028       default:
13029         return;
13030     }
13031
13032     pausing = FALSE;
13033     StopClocks();
13034     first.offeredDraw = second.offeredDraw = 0;
13035
13036     if (gameMode == PlayFromGameFile) {
13037         whiteTimeRemaining = timeRemaining[0][currentMove];
13038         blackTimeRemaining = timeRemaining[1][currentMove];
13039         DisplayTitle("");
13040     }
13041
13042     if (gameMode == MachinePlaysWhite ||
13043         gameMode == MachinePlaysBlack ||
13044         gameMode == TwoMachinesPlay ||
13045         gameMode == EndOfGame) {
13046         i = forwardMostMove;
13047         while (i > currentMove) {
13048             SendToProgram("undo\n", &first);
13049             i--;
13050         }
13051         whiteTimeRemaining = timeRemaining[0][currentMove];
13052         blackTimeRemaining = timeRemaining[1][currentMove];
13053         DisplayBothClocks();
13054         if (whiteFlag || blackFlag) {
13055             whiteFlag = blackFlag = 0;
13056         }
13057         DisplayTitle("");
13058     }
13059
13060     gameMode = EditGame;
13061     ModeHighlight();
13062     SetGameInfo();
13063 }
13064
13065
13066 void
13067 EditPositionEvent()
13068 {
13069     if (gameMode == EditPosition) {
13070         EditGameEvent();
13071         return;
13072     }
13073
13074     EditGameEvent();
13075     if (gameMode != EditGame) return;
13076
13077     gameMode = EditPosition;
13078     ModeHighlight();
13079     SetGameInfo();
13080     if (currentMove > 0)
13081       CopyBoard(boards[0], boards[currentMove]);
13082
13083     blackPlaysFirst = !WhiteOnMove(currentMove);
13084     ResetClocks();
13085     currentMove = forwardMostMove = backwardMostMove = 0;
13086     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13087     DisplayMove(-1);
13088 }
13089
13090 void
13091 ExitAnalyzeMode()
13092 {
13093     /* [DM] icsEngineAnalyze - possible call from other functions */
13094     if (appData.icsEngineAnalyze) {
13095         appData.icsEngineAnalyze = FALSE;
13096
13097         DisplayMessage("",_("Close ICS engine analyze..."));
13098     }
13099     if (first.analysisSupport && first.analyzing) {
13100       SendToProgram("exit\n", &first);
13101       first.analyzing = FALSE;
13102     }
13103     thinkOutput[0] = NULLCHAR;
13104 }
13105
13106 void
13107 EditPositionDone(Boolean fakeRights)
13108 {
13109     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13110
13111     startedFromSetupPosition = TRUE;
13112     InitChessProgram(&first, FALSE);
13113     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13114       boards[0][EP_STATUS] = EP_NONE;
13115       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13116     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13117         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13118         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13119       } else boards[0][CASTLING][2] = NoRights;
13120     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13121         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13122         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13123       } else boards[0][CASTLING][5] = NoRights;
13124     }
13125     SendToProgram("force\n", &first);
13126     if (blackPlaysFirst) {
13127         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13128         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13129         currentMove = forwardMostMove = backwardMostMove = 1;
13130         CopyBoard(boards[1], boards[0]);
13131     } else {
13132         currentMove = forwardMostMove = backwardMostMove = 0;
13133     }
13134     SendBoard(&first, forwardMostMove);
13135     if (appData.debugMode) {
13136         fprintf(debugFP, "EditPosDone\n");
13137     }
13138     DisplayTitle("");
13139     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13140     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13141     gameMode = EditGame;
13142     ModeHighlight();
13143     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13144     ClearHighlights(); /* [AS] */
13145 }
13146
13147 /* Pause for `ms' milliseconds */
13148 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13149 void
13150 TimeDelay(ms)
13151      long ms;
13152 {
13153     TimeMark m1, m2;
13154
13155     GetTimeMark(&m1);
13156     do {
13157         GetTimeMark(&m2);
13158     } while (SubtractTimeMarks(&m2, &m1) < ms);
13159 }
13160
13161 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13162 void
13163 SendMultiLineToICS(buf)
13164      char *buf;
13165 {
13166     char temp[MSG_SIZ+1], *p;
13167     int len;
13168
13169     len = strlen(buf);
13170     if (len > MSG_SIZ)
13171       len = MSG_SIZ;
13172
13173     strncpy(temp, buf, len);
13174     temp[len] = 0;
13175
13176     p = temp;
13177     while (*p) {
13178         if (*p == '\n' || *p == '\r')
13179           *p = ' ';
13180         ++p;
13181     }
13182
13183     strcat(temp, "\n");
13184     SendToICS(temp);
13185     SendToPlayer(temp, strlen(temp));
13186 }
13187
13188 void
13189 SetWhiteToPlayEvent()
13190 {
13191     if (gameMode == EditPosition) {
13192         blackPlaysFirst = FALSE;
13193         DisplayBothClocks();    /* works because currentMove is 0 */
13194     } else if (gameMode == IcsExamining) {
13195         SendToICS(ics_prefix);
13196         SendToICS("tomove white\n");
13197     }
13198 }
13199
13200 void
13201 SetBlackToPlayEvent()
13202 {
13203     if (gameMode == EditPosition) {
13204         blackPlaysFirst = TRUE;
13205         currentMove = 1;        /* kludge */
13206         DisplayBothClocks();
13207         currentMove = 0;
13208     } else if (gameMode == IcsExamining) {
13209         SendToICS(ics_prefix);
13210         SendToICS("tomove black\n");
13211     }
13212 }
13213
13214 void
13215 EditPositionMenuEvent(selection, x, y)
13216      ChessSquare selection;
13217      int x, y;
13218 {
13219     char buf[MSG_SIZ];
13220     ChessSquare piece = boards[0][y][x];
13221
13222     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13223
13224     switch (selection) {
13225       case ClearBoard:
13226         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13227             SendToICS(ics_prefix);
13228             SendToICS("bsetup clear\n");
13229         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13230             SendToICS(ics_prefix);
13231             SendToICS("clearboard\n");
13232         } else {
13233             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13234                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13235                 for (y = 0; y < BOARD_HEIGHT; y++) {
13236                     if (gameMode == IcsExamining) {
13237                         if (boards[currentMove][y][x] != EmptySquare) {
13238                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13239                                     AAA + x, ONE + y);
13240                             SendToICS(buf);
13241                         }
13242                     } else {
13243                         boards[0][y][x] = p;
13244                     }
13245                 }
13246             }
13247         }
13248         if (gameMode == EditPosition) {
13249             DrawPosition(FALSE, boards[0]);
13250         }
13251         break;
13252
13253       case WhitePlay:
13254         SetWhiteToPlayEvent();
13255         break;
13256
13257       case BlackPlay:
13258         SetBlackToPlayEvent();
13259         break;
13260
13261       case EmptySquare:
13262         if (gameMode == IcsExamining) {
13263             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13264             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13265             SendToICS(buf);
13266         } else {
13267             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13268                 if(x == BOARD_LEFT-2) {
13269                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13270                     boards[0][y][1] = 0;
13271                 } else
13272                 if(x == BOARD_RGHT+1) {
13273                     if(y >= gameInfo.holdingsSize) break;
13274                     boards[0][y][BOARD_WIDTH-2] = 0;
13275                 } else break;
13276             }
13277             boards[0][y][x] = EmptySquare;
13278             DrawPosition(FALSE, boards[0]);
13279         }
13280         break;
13281
13282       case PromotePiece:
13283         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13284            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13285             selection = (ChessSquare) (PROMOTED piece);
13286         } else if(piece == EmptySquare) selection = WhiteSilver;
13287         else selection = (ChessSquare)((int)piece - 1);
13288         goto defaultlabel;
13289
13290       case DemotePiece:
13291         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13292            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13293             selection = (ChessSquare) (DEMOTED piece);
13294         } else if(piece == EmptySquare) selection = BlackSilver;
13295         else selection = (ChessSquare)((int)piece + 1);
13296         goto defaultlabel;
13297
13298       case WhiteQueen:
13299       case BlackQueen:
13300         if(gameInfo.variant == VariantShatranj ||
13301            gameInfo.variant == VariantXiangqi  ||
13302            gameInfo.variant == VariantCourier  ||
13303            gameInfo.variant == VariantMakruk     )
13304             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13305         goto defaultlabel;
13306
13307       case WhiteKing:
13308       case BlackKing:
13309         if(gameInfo.variant == VariantXiangqi)
13310             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13311         if(gameInfo.variant == VariantKnightmate)
13312             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13313       default:
13314         defaultlabel:
13315         if (gameMode == IcsExamining) {
13316             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13317             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13318                      PieceToChar(selection), AAA + x, ONE + y);
13319             SendToICS(buf);
13320         } else {
13321             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13322                 int n;
13323                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13324                     n = PieceToNumber(selection - BlackPawn);
13325                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13326                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13327                     boards[0][BOARD_HEIGHT-1-n][1]++;
13328                 } else
13329                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13330                     n = PieceToNumber(selection);
13331                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13332                     boards[0][n][BOARD_WIDTH-1] = selection;
13333                     boards[0][n][BOARD_WIDTH-2]++;
13334                 }
13335             } else
13336             boards[0][y][x] = selection;
13337             DrawPosition(TRUE, boards[0]);
13338         }
13339         break;
13340     }
13341 }
13342
13343
13344 void
13345 DropMenuEvent(selection, x, y)
13346      ChessSquare selection;
13347      int x, y;
13348 {
13349     ChessMove moveType;
13350
13351     switch (gameMode) {
13352       case IcsPlayingWhite:
13353       case MachinePlaysBlack:
13354         if (!WhiteOnMove(currentMove)) {
13355             DisplayMoveError(_("It is Black's turn"));
13356             return;
13357         }
13358         moveType = WhiteDrop;
13359         break;
13360       case IcsPlayingBlack:
13361       case MachinePlaysWhite:
13362         if (WhiteOnMove(currentMove)) {
13363             DisplayMoveError(_("It is White's turn"));
13364             return;
13365         }
13366         moveType = BlackDrop;
13367         break;
13368       case EditGame:
13369         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13370         break;
13371       default:
13372         return;
13373     }
13374
13375     if (moveType == BlackDrop && selection < BlackPawn) {
13376       selection = (ChessSquare) ((int) selection
13377                                  + (int) BlackPawn - (int) WhitePawn);
13378     }
13379     if (boards[currentMove][y][x] != EmptySquare) {
13380         DisplayMoveError(_("That square is occupied"));
13381         return;
13382     }
13383
13384     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13385 }
13386
13387 void
13388 AcceptEvent()
13389 {
13390     /* Accept a pending offer of any kind from opponent */
13391
13392     if (appData.icsActive) {
13393         SendToICS(ics_prefix);
13394         SendToICS("accept\n");
13395     } else if (cmailMsgLoaded) {
13396         if (currentMove == cmailOldMove &&
13397             commentList[cmailOldMove] != NULL &&
13398             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13399                    "Black offers a draw" : "White offers a draw")) {
13400             TruncateGame();
13401             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13402             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13403         } else {
13404             DisplayError(_("There is no pending offer on this move"), 0);
13405             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13406         }
13407     } else {
13408         /* Not used for offers from chess program */
13409     }
13410 }
13411
13412 void
13413 DeclineEvent()
13414 {
13415     /* Decline a pending offer of any kind from opponent */
13416
13417     if (appData.icsActive) {
13418         SendToICS(ics_prefix);
13419         SendToICS("decline\n");
13420     } else if (cmailMsgLoaded) {
13421         if (currentMove == cmailOldMove &&
13422             commentList[cmailOldMove] != NULL &&
13423             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13424                    "Black offers a draw" : "White offers a draw")) {
13425 #ifdef NOTDEF
13426             AppendComment(cmailOldMove, "Draw declined", TRUE);
13427             DisplayComment(cmailOldMove - 1, "Draw declined");
13428 #endif /*NOTDEF*/
13429         } else {
13430             DisplayError(_("There is no pending offer on this move"), 0);
13431         }
13432     } else {
13433         /* Not used for offers from chess program */
13434     }
13435 }
13436
13437 void
13438 RematchEvent()
13439 {
13440     /* Issue ICS rematch command */
13441     if (appData.icsActive) {
13442         SendToICS(ics_prefix);
13443         SendToICS("rematch\n");
13444     }
13445 }
13446
13447 void
13448 CallFlagEvent()
13449 {
13450     /* Call your opponent's flag (claim a win on time) */
13451     if (appData.icsActive) {
13452         SendToICS(ics_prefix);
13453         SendToICS("flag\n");
13454     } else {
13455         switch (gameMode) {
13456           default:
13457             return;
13458           case MachinePlaysWhite:
13459             if (whiteFlag) {
13460                 if (blackFlag)
13461                   GameEnds(GameIsDrawn, "Both players ran out of time",
13462                            GE_PLAYER);
13463                 else
13464                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13465             } else {
13466                 DisplayError(_("Your opponent is not out of time"), 0);
13467             }
13468             break;
13469           case MachinePlaysBlack:
13470             if (blackFlag) {
13471                 if (whiteFlag)
13472                   GameEnds(GameIsDrawn, "Both players ran out of time",
13473                            GE_PLAYER);
13474                 else
13475                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13476             } else {
13477                 DisplayError(_("Your opponent is not out of time"), 0);
13478             }
13479             break;
13480         }
13481     }
13482 }
13483
13484 void
13485 ClockClick(int which)
13486 {       // [HGM] code moved to back-end from winboard.c
13487         if(which) { // black clock
13488           if (gameMode == EditPosition || gameMode == IcsExamining) {
13489             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13490             SetBlackToPlayEvent();
13491           } else if (gameMode == EditGame || shiftKey) {
13492             AdjustClock(which, -1);
13493           } else if (gameMode == IcsPlayingWhite ||
13494                      gameMode == MachinePlaysBlack) {
13495             CallFlagEvent();
13496           }
13497         } else { // white clock
13498           if (gameMode == EditPosition || gameMode == IcsExamining) {
13499             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13500             SetWhiteToPlayEvent();
13501           } else if (gameMode == EditGame || shiftKey) {
13502             AdjustClock(which, -1);
13503           } else if (gameMode == IcsPlayingBlack ||
13504                    gameMode == MachinePlaysWhite) {
13505             CallFlagEvent();
13506           }
13507         }
13508 }
13509
13510 void
13511 DrawEvent()
13512 {
13513     /* Offer draw or accept pending draw offer from opponent */
13514
13515     if (appData.icsActive) {
13516         /* Note: tournament rules require draw offers to be
13517            made after you make your move but before you punch
13518            your clock.  Currently ICS doesn't let you do that;
13519            instead, you immediately punch your clock after making
13520            a move, but you can offer a draw at any time. */
13521
13522         SendToICS(ics_prefix);
13523         SendToICS("draw\n");
13524         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13525     } else if (cmailMsgLoaded) {
13526         if (currentMove == cmailOldMove &&
13527             commentList[cmailOldMove] != NULL &&
13528             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13529                    "Black offers a draw" : "White offers a draw")) {
13530             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13531             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13532         } else if (currentMove == cmailOldMove + 1) {
13533             char *offer = WhiteOnMove(cmailOldMove) ?
13534               "White offers a draw" : "Black offers a draw";
13535             AppendComment(currentMove, offer, TRUE);
13536             DisplayComment(currentMove - 1, offer);
13537             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13538         } else {
13539             DisplayError(_("You must make your move before offering a draw"), 0);
13540             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13541         }
13542     } else if (first.offeredDraw) {
13543         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13544     } else {
13545         if (first.sendDrawOffers) {
13546             SendToProgram("draw\n", &first);
13547             userOfferedDraw = TRUE;
13548         }
13549     }
13550 }
13551
13552 void
13553 AdjournEvent()
13554 {
13555     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13556
13557     if (appData.icsActive) {
13558         SendToICS(ics_prefix);
13559         SendToICS("adjourn\n");
13560     } else {
13561         /* Currently GNU Chess doesn't offer or accept Adjourns */
13562     }
13563 }
13564
13565
13566 void
13567 AbortEvent()
13568 {
13569     /* Offer Abort or accept pending Abort offer from opponent */
13570
13571     if (appData.icsActive) {
13572         SendToICS(ics_prefix);
13573         SendToICS("abort\n");
13574     } else {
13575         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13576     }
13577 }
13578
13579 void
13580 ResignEvent()
13581 {
13582     /* Resign.  You can do this even if it's not your turn. */
13583
13584     if (appData.icsActive) {
13585         SendToICS(ics_prefix);
13586         SendToICS("resign\n");
13587     } else {
13588         switch (gameMode) {
13589           case MachinePlaysWhite:
13590             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13591             break;
13592           case MachinePlaysBlack:
13593             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13594             break;
13595           case EditGame:
13596             if (cmailMsgLoaded) {
13597                 TruncateGame();
13598                 if (WhiteOnMove(cmailOldMove)) {
13599                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13600                 } else {
13601                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13602                 }
13603                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13604             }
13605             break;
13606           default:
13607             break;
13608         }
13609     }
13610 }
13611
13612
13613 void
13614 StopObservingEvent()
13615 {
13616     /* Stop observing current games */
13617     SendToICS(ics_prefix);
13618     SendToICS("unobserve\n");
13619 }
13620
13621 void
13622 StopExaminingEvent()
13623 {
13624     /* Stop observing current game */
13625     SendToICS(ics_prefix);
13626     SendToICS("unexamine\n");
13627 }
13628
13629 void
13630 ForwardInner(target)
13631      int target;
13632 {
13633     int limit;
13634
13635     if (appData.debugMode)
13636         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13637                 target, currentMove, forwardMostMove);
13638
13639     if (gameMode == EditPosition)
13640       return;
13641
13642     if (gameMode == PlayFromGameFile && !pausing)
13643       PauseEvent();
13644
13645     if (gameMode == IcsExamining && pausing)
13646       limit = pauseExamForwardMostMove;
13647     else
13648       limit = forwardMostMove;
13649
13650     if (target > limit) target = limit;
13651
13652     if (target > 0 && moveList[target - 1][0]) {
13653         int fromX, fromY, toX, toY;
13654         toX = moveList[target - 1][2] - AAA;
13655         toY = moveList[target - 1][3] - ONE;
13656         if (moveList[target - 1][1] == '@') {
13657             if (appData.highlightLastMove) {
13658                 SetHighlights(-1, -1, toX, toY);
13659             }
13660         } else {
13661             fromX = moveList[target - 1][0] - AAA;
13662             fromY = moveList[target - 1][1] - ONE;
13663             if (target == currentMove + 1) {
13664                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13665             }
13666             if (appData.highlightLastMove) {
13667                 SetHighlights(fromX, fromY, toX, toY);
13668             }
13669         }
13670     }
13671     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13672         gameMode == Training || gameMode == PlayFromGameFile ||
13673         gameMode == AnalyzeFile) {
13674         while (currentMove < target) {
13675             SendMoveToProgram(currentMove++, &first);
13676         }
13677     } else {
13678         currentMove = target;
13679     }
13680
13681     if (gameMode == EditGame || gameMode == EndOfGame) {
13682         whiteTimeRemaining = timeRemaining[0][currentMove];
13683         blackTimeRemaining = timeRemaining[1][currentMove];
13684     }
13685     DisplayBothClocks();
13686     DisplayMove(currentMove - 1);
13687     DrawPosition(FALSE, boards[currentMove]);
13688     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13689     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13690         DisplayComment(currentMove - 1, commentList[currentMove]);
13691     }
13692     DisplayBook(currentMove);
13693 }
13694
13695
13696 void
13697 ForwardEvent()
13698 {
13699     if (gameMode == IcsExamining && !pausing) {
13700         SendToICS(ics_prefix);
13701         SendToICS("forward\n");
13702     } else {
13703         ForwardInner(currentMove + 1);
13704     }
13705 }
13706
13707 void
13708 ToEndEvent()
13709 {
13710     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13711         /* to optimze, we temporarily turn off analysis mode while we feed
13712          * the remaining moves to the engine. Otherwise we get analysis output
13713          * after each move.
13714          */
13715         if (first.analysisSupport) {
13716           SendToProgram("exit\nforce\n", &first);
13717           first.analyzing = FALSE;
13718         }
13719     }
13720
13721     if (gameMode == IcsExamining && !pausing) {
13722         SendToICS(ics_prefix);
13723         SendToICS("forward 999999\n");
13724     } else {
13725         ForwardInner(forwardMostMove);
13726     }
13727
13728     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13729         /* we have fed all the moves, so reactivate analysis mode */
13730         SendToProgram("analyze\n", &first);
13731         first.analyzing = TRUE;
13732         /*first.maybeThinking = TRUE;*/
13733         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13734     }
13735 }
13736
13737 void
13738 BackwardInner(target)
13739      int target;
13740 {
13741     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13742
13743     if (appData.debugMode)
13744         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13745                 target, currentMove, forwardMostMove);
13746
13747     if (gameMode == EditPosition) return;
13748     if (currentMove <= backwardMostMove) {
13749         ClearHighlights();
13750         DrawPosition(full_redraw, boards[currentMove]);
13751         return;
13752     }
13753     if (gameMode == PlayFromGameFile && !pausing)
13754       PauseEvent();
13755
13756     if (moveList[target][0]) {
13757         int fromX, fromY, toX, toY;
13758         toX = moveList[target][2] - AAA;
13759         toY = moveList[target][3] - ONE;
13760         if (moveList[target][1] == '@') {
13761             if (appData.highlightLastMove) {
13762                 SetHighlights(-1, -1, toX, toY);
13763             }
13764         } else {
13765             fromX = moveList[target][0] - AAA;
13766             fromY = moveList[target][1] - ONE;
13767             if (target == currentMove - 1) {
13768                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13769             }
13770             if (appData.highlightLastMove) {
13771                 SetHighlights(fromX, fromY, toX, toY);
13772             }
13773         }
13774     }
13775     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13776         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13777         while (currentMove > target) {
13778             SendToProgram("undo\n", &first);
13779             currentMove--;
13780         }
13781     } else {
13782         currentMove = target;
13783     }
13784
13785     if (gameMode == EditGame || gameMode == EndOfGame) {
13786         whiteTimeRemaining = timeRemaining[0][currentMove];
13787         blackTimeRemaining = timeRemaining[1][currentMove];
13788     }
13789     DisplayBothClocks();
13790     DisplayMove(currentMove - 1);
13791     DrawPosition(full_redraw, boards[currentMove]);
13792     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13793     // [HGM] PV info: routine tests if comment empty
13794     DisplayComment(currentMove - 1, commentList[currentMove]);
13795     DisplayBook(currentMove);
13796 }
13797
13798 void
13799 BackwardEvent()
13800 {
13801     if (gameMode == IcsExamining && !pausing) {
13802         SendToICS(ics_prefix);
13803         SendToICS("backward\n");
13804     } else {
13805         BackwardInner(currentMove - 1);
13806     }
13807 }
13808
13809 void
13810 ToStartEvent()
13811 {
13812     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13813         /* to optimize, we temporarily turn off analysis mode while we undo
13814          * all the moves. Otherwise we get analysis output after each undo.
13815          */
13816         if (first.analysisSupport) {
13817           SendToProgram("exit\nforce\n", &first);
13818           first.analyzing = FALSE;
13819         }
13820     }
13821
13822     if (gameMode == IcsExamining && !pausing) {
13823         SendToICS(ics_prefix);
13824         SendToICS("backward 999999\n");
13825     } else {
13826         BackwardInner(backwardMostMove);
13827     }
13828
13829     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13830         /* we have fed all the moves, so reactivate analysis mode */
13831         SendToProgram("analyze\n", &first);
13832         first.analyzing = TRUE;
13833         /*first.maybeThinking = TRUE;*/
13834         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13835     }
13836 }
13837
13838 void
13839 ToNrEvent(int to)
13840 {
13841   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13842   if (to >= forwardMostMove) to = forwardMostMove;
13843   if (to <= backwardMostMove) to = backwardMostMove;
13844   if (to < currentMove) {
13845     BackwardInner(to);
13846   } else {
13847     ForwardInner(to);
13848   }
13849 }
13850
13851 void
13852 RevertEvent(Boolean annotate)
13853 {
13854     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13855         return;
13856     }
13857     if (gameMode != IcsExamining) {
13858         DisplayError(_("You are not examining a game"), 0);
13859         return;
13860     }
13861     if (pausing) {
13862         DisplayError(_("You can't revert while pausing"), 0);
13863         return;
13864     }
13865     SendToICS(ics_prefix);
13866     SendToICS("revert\n");
13867 }
13868
13869 void
13870 RetractMoveEvent()
13871 {
13872     switch (gameMode) {
13873       case MachinePlaysWhite:
13874       case MachinePlaysBlack:
13875         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13876             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13877             return;
13878         }
13879         if (forwardMostMove < 2) return;
13880         currentMove = forwardMostMove = forwardMostMove - 2;
13881         whiteTimeRemaining = timeRemaining[0][currentMove];
13882         blackTimeRemaining = timeRemaining[1][currentMove];
13883         DisplayBothClocks();
13884         DisplayMove(currentMove - 1);
13885         ClearHighlights();/*!! could figure this out*/
13886         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13887         SendToProgram("remove\n", &first);
13888         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13889         break;
13890
13891       case BeginningOfGame:
13892       default:
13893         break;
13894
13895       case IcsPlayingWhite:
13896       case IcsPlayingBlack:
13897         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13898             SendToICS(ics_prefix);
13899             SendToICS("takeback 2\n");
13900         } else {
13901             SendToICS(ics_prefix);
13902             SendToICS("takeback 1\n");
13903         }
13904         break;
13905     }
13906 }
13907
13908 void
13909 MoveNowEvent()
13910 {
13911     ChessProgramState *cps;
13912
13913     switch (gameMode) {
13914       case MachinePlaysWhite:
13915         if (!WhiteOnMove(forwardMostMove)) {
13916             DisplayError(_("It is your turn"), 0);
13917             return;
13918         }
13919         cps = &first;
13920         break;
13921       case MachinePlaysBlack:
13922         if (WhiteOnMove(forwardMostMove)) {
13923             DisplayError(_("It is your turn"), 0);
13924             return;
13925         }
13926         cps = &first;
13927         break;
13928       case TwoMachinesPlay:
13929         if (WhiteOnMove(forwardMostMove) ==
13930             (first.twoMachinesColor[0] == 'w')) {
13931             cps = &first;
13932         } else {
13933             cps = &second;
13934         }
13935         break;
13936       case BeginningOfGame:
13937       default:
13938         return;
13939     }
13940     SendToProgram("?\n", cps);
13941 }
13942
13943 void
13944 TruncateGameEvent()
13945 {
13946     EditGameEvent();
13947     if (gameMode != EditGame) return;
13948     TruncateGame();
13949 }
13950
13951 void
13952 TruncateGame()
13953 {
13954     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13955     if (forwardMostMove > currentMove) {
13956         if (gameInfo.resultDetails != NULL) {
13957             free(gameInfo.resultDetails);
13958             gameInfo.resultDetails = NULL;
13959             gameInfo.result = GameUnfinished;
13960         }
13961         forwardMostMove = currentMove;
13962         HistorySet(parseList, backwardMostMove, forwardMostMove,
13963                    currentMove-1);
13964     }
13965 }
13966
13967 void
13968 HintEvent()
13969 {
13970     if (appData.noChessProgram) return;
13971     switch (gameMode) {
13972       case MachinePlaysWhite:
13973         if (WhiteOnMove(forwardMostMove)) {
13974             DisplayError(_("Wait until your turn"), 0);
13975             return;
13976         }
13977         break;
13978       case BeginningOfGame:
13979       case MachinePlaysBlack:
13980         if (!WhiteOnMove(forwardMostMove)) {
13981             DisplayError(_("Wait until your turn"), 0);
13982             return;
13983         }
13984         break;
13985       default:
13986         DisplayError(_("No hint available"), 0);
13987         return;
13988     }
13989     SendToProgram("hint\n", &first);
13990     hintRequested = TRUE;
13991 }
13992
13993 void
13994 BookEvent()
13995 {
13996     if (appData.noChessProgram) return;
13997     switch (gameMode) {
13998       case MachinePlaysWhite:
13999         if (WhiteOnMove(forwardMostMove)) {
14000             DisplayError(_("Wait until your turn"), 0);
14001             return;
14002         }
14003         break;
14004       case BeginningOfGame:
14005       case MachinePlaysBlack:
14006         if (!WhiteOnMove(forwardMostMove)) {
14007             DisplayError(_("Wait until your turn"), 0);
14008             return;
14009         }
14010         break;
14011       case EditPosition:
14012         EditPositionDone(TRUE);
14013         break;
14014       case TwoMachinesPlay:
14015         return;
14016       default:
14017         break;
14018     }
14019     SendToProgram("bk\n", &first);
14020     bookOutput[0] = NULLCHAR;
14021     bookRequested = TRUE;
14022 }
14023
14024 void
14025 AboutGameEvent()
14026 {
14027     char *tags = PGNTags(&gameInfo);
14028     TagsPopUp(tags, CmailMsg());
14029     free(tags);
14030 }
14031
14032 /* end button procedures */
14033
14034 void
14035 PrintPosition(fp, move)
14036      FILE *fp;
14037      int move;
14038 {
14039     int i, j;
14040
14041     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14042         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14043             char c = PieceToChar(boards[move][i][j]);
14044             fputc(c == 'x' ? '.' : c, fp);
14045             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14046         }
14047     }
14048     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14049       fprintf(fp, "white to play\n");
14050     else
14051       fprintf(fp, "black to play\n");
14052 }
14053
14054 void
14055 PrintOpponents(fp)
14056      FILE *fp;
14057 {
14058     if (gameInfo.white != NULL) {
14059         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14060     } else {
14061         fprintf(fp, "\n");
14062     }
14063 }
14064
14065 /* Find last component of program's own name, using some heuristics */
14066 void
14067 TidyProgramName(prog, host, buf)
14068      char *prog, *host, buf[MSG_SIZ];
14069 {
14070     char *p, *q;
14071     int local = (strcmp(host, "localhost") == 0);
14072     while (!local && (p = strchr(prog, ';')) != NULL) {
14073         p++;
14074         while (*p == ' ') p++;
14075         prog = p;
14076     }
14077     if (*prog == '"' || *prog == '\'') {
14078         q = strchr(prog + 1, *prog);
14079     } else {
14080         q = strchr(prog, ' ');
14081     }
14082     if (q == NULL) q = prog + strlen(prog);
14083     p = q;
14084     while (p >= prog && *p != '/' && *p != '\\') p--;
14085     p++;
14086     if(p == prog && *p == '"') p++;
14087     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14088     memcpy(buf, p, q - p);
14089     buf[q - p] = NULLCHAR;
14090     if (!local) {
14091         strcat(buf, "@");
14092         strcat(buf, host);
14093     }
14094 }
14095
14096 char *
14097 TimeControlTagValue()
14098 {
14099     char buf[MSG_SIZ];
14100     if (!appData.clockMode) {
14101       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14102     } else if (movesPerSession > 0) {
14103       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14104     } else if (timeIncrement == 0) {
14105       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14106     } else {
14107       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14108     }
14109     return StrSave(buf);
14110 }
14111
14112 void
14113 SetGameInfo()
14114 {
14115     /* This routine is used only for certain modes */
14116     VariantClass v = gameInfo.variant;
14117     ChessMove r = GameUnfinished;
14118     char *p = NULL;
14119
14120     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14121         r = gameInfo.result;
14122         p = gameInfo.resultDetails;
14123         gameInfo.resultDetails = NULL;
14124     }
14125     ClearGameInfo(&gameInfo);
14126     gameInfo.variant = v;
14127
14128     switch (gameMode) {
14129       case MachinePlaysWhite:
14130         gameInfo.event = StrSave( appData.pgnEventHeader );
14131         gameInfo.site = StrSave(HostName());
14132         gameInfo.date = PGNDate();
14133         gameInfo.round = StrSave("-");
14134         gameInfo.white = StrSave(first.tidy);
14135         gameInfo.black = StrSave(UserName());
14136         gameInfo.timeControl = TimeControlTagValue();
14137         break;
14138
14139       case MachinePlaysBlack:
14140         gameInfo.event = StrSave( appData.pgnEventHeader );
14141         gameInfo.site = StrSave(HostName());
14142         gameInfo.date = PGNDate();
14143         gameInfo.round = StrSave("-");
14144         gameInfo.white = StrSave(UserName());
14145         gameInfo.black = StrSave(first.tidy);
14146         gameInfo.timeControl = TimeControlTagValue();
14147         break;
14148
14149       case TwoMachinesPlay:
14150         gameInfo.event = StrSave( appData.pgnEventHeader );
14151         gameInfo.site = StrSave(HostName());
14152         gameInfo.date = PGNDate();
14153         if (roundNr > 0) {
14154             char buf[MSG_SIZ];
14155             snprintf(buf, MSG_SIZ, "%d", roundNr);
14156             gameInfo.round = StrSave(buf);
14157         } else {
14158             gameInfo.round = StrSave("-");
14159         }
14160         if (first.twoMachinesColor[0] == 'w') {
14161             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14162             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14163         } else {
14164             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14165             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14166         }
14167         gameInfo.timeControl = TimeControlTagValue();
14168         break;
14169
14170       case EditGame:
14171         gameInfo.event = StrSave("Edited game");
14172         gameInfo.site = StrSave(HostName());
14173         gameInfo.date = PGNDate();
14174         gameInfo.round = StrSave("-");
14175         gameInfo.white = StrSave("-");
14176         gameInfo.black = StrSave("-");
14177         gameInfo.result = r;
14178         gameInfo.resultDetails = p;
14179         break;
14180
14181       case EditPosition:
14182         gameInfo.event = StrSave("Edited position");
14183         gameInfo.site = StrSave(HostName());
14184         gameInfo.date = PGNDate();
14185         gameInfo.round = StrSave("-");
14186         gameInfo.white = StrSave("-");
14187         gameInfo.black = StrSave("-");
14188         break;
14189
14190       case IcsPlayingWhite:
14191       case IcsPlayingBlack:
14192       case IcsObserving:
14193       case IcsExamining:
14194         break;
14195
14196       case PlayFromGameFile:
14197         gameInfo.event = StrSave("Game from non-PGN file");
14198         gameInfo.site = StrSave(HostName());
14199         gameInfo.date = PGNDate();
14200         gameInfo.round = StrSave("-");
14201         gameInfo.white = StrSave("?");
14202         gameInfo.black = StrSave("?");
14203         break;
14204
14205       default:
14206         break;
14207     }
14208 }
14209
14210 void
14211 ReplaceComment(index, text)
14212      int index;
14213      char *text;
14214 {
14215     int len;
14216     char *p;
14217     float score;
14218
14219     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14220        pvInfoList[index-1].depth == len &&
14221        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14222        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14223     while (*text == '\n') text++;
14224     len = strlen(text);
14225     while (len > 0 && text[len - 1] == '\n') len--;
14226
14227     if (commentList[index] != NULL)
14228       free(commentList[index]);
14229
14230     if (len == 0) {
14231         commentList[index] = NULL;
14232         return;
14233     }
14234   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14235       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14236       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14237     commentList[index] = (char *) malloc(len + 2);
14238     strncpy(commentList[index], text, len);
14239     commentList[index][len] = '\n';
14240     commentList[index][len + 1] = NULLCHAR;
14241   } else {
14242     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14243     char *p;
14244     commentList[index] = (char *) malloc(len + 7);
14245     safeStrCpy(commentList[index], "{\n", 3);
14246     safeStrCpy(commentList[index]+2, text, len+1);
14247     commentList[index][len+2] = NULLCHAR;
14248     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14249     strcat(commentList[index], "\n}\n");
14250   }
14251 }
14252
14253 void
14254 CrushCRs(text)
14255      char *text;
14256 {
14257   char *p = text;
14258   char *q = text;
14259   char ch;
14260
14261   do {
14262     ch = *p++;
14263     if (ch == '\r') continue;
14264     *q++ = ch;
14265   } while (ch != '\0');
14266 }
14267
14268 void
14269 AppendComment(index, text, addBraces)
14270      int index;
14271      char *text;
14272      Boolean addBraces; // [HGM] braces: tells if we should add {}
14273 {
14274     int oldlen, len;
14275     char *old;
14276
14277 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14278     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14279
14280     CrushCRs(text);
14281     while (*text == '\n') text++;
14282     len = strlen(text);
14283     while (len > 0 && text[len - 1] == '\n') len--;
14284
14285     if (len == 0) return;
14286
14287     if (commentList[index] != NULL) {
14288         old = commentList[index];
14289         oldlen = strlen(old);
14290         while(commentList[index][oldlen-1] ==  '\n')
14291           commentList[index][--oldlen] = NULLCHAR;
14292         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14293         safeStrCpy(commentList[index], old, oldlen + len + 6);
14294         free(old);
14295         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14296         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14297           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14298           while (*text == '\n') { text++; len--; }
14299           commentList[index][--oldlen] = NULLCHAR;
14300       }
14301         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14302         else          strcat(commentList[index], "\n");
14303         strcat(commentList[index], text);
14304         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14305         else          strcat(commentList[index], "\n");
14306     } else {
14307         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14308         if(addBraces)
14309           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14310         else commentList[index][0] = NULLCHAR;
14311         strcat(commentList[index], text);
14312         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14313         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14314     }
14315 }
14316
14317 static char * FindStr( char * text, char * sub_text )
14318 {
14319     char * result = strstr( text, sub_text );
14320
14321     if( result != NULL ) {
14322         result += strlen( sub_text );
14323     }
14324
14325     return result;
14326 }
14327
14328 /* [AS] Try to extract PV info from PGN comment */
14329 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14330 char *GetInfoFromComment( int index, char * text )
14331 {
14332     char * sep = text, *p;
14333
14334     if( text != NULL && index > 0 ) {
14335         int score = 0;
14336         int depth = 0;
14337         int time = -1, sec = 0, deci;
14338         char * s_eval = FindStr( text, "[%eval " );
14339         char * s_emt = FindStr( text, "[%emt " );
14340
14341         if( s_eval != NULL || s_emt != NULL ) {
14342             /* New style */
14343             char delim;
14344
14345             if( s_eval != NULL ) {
14346                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14347                     return text;
14348                 }
14349
14350                 if( delim != ']' ) {
14351                     return text;
14352                 }
14353             }
14354
14355             if( s_emt != NULL ) {
14356             }
14357                 return text;
14358         }
14359         else {
14360             /* We expect something like: [+|-]nnn.nn/dd */
14361             int score_lo = 0;
14362
14363             if(*text != '{') return text; // [HGM] braces: must be normal comment
14364
14365             sep = strchr( text, '/' );
14366             if( sep == NULL || sep < (text+4) ) {
14367                 return text;
14368             }
14369
14370             p = text;
14371             if(p[1] == '(') { // comment starts with PV
14372                p = strchr(p, ')'); // locate end of PV
14373                if(p == NULL || sep < p+5) return text;
14374                // at this point we have something like "{(.*) +0.23/6 ..."
14375                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14376                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14377                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14378             }
14379             time = -1; sec = -1; deci = -1;
14380             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14381                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14382                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14383                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14384                 return text;
14385             }
14386
14387             if( score_lo < 0 || score_lo >= 100 ) {
14388                 return text;
14389             }
14390
14391             if(sec >= 0) time = 600*time + 10*sec; else
14392             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14393
14394             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14395
14396             /* [HGM] PV time: now locate end of PV info */
14397             while( *++sep >= '0' && *sep <= '9'); // strip depth
14398             if(time >= 0)
14399             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14400             if(sec >= 0)
14401             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14402             if(deci >= 0)
14403             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14404             while(*sep == ' ') sep++;
14405         }
14406
14407         if( depth <= 0 ) {
14408             return text;
14409         }
14410
14411         if( time < 0 ) {
14412             time = -1;
14413         }
14414
14415         pvInfoList[index-1].depth = depth;
14416         pvInfoList[index-1].score = score;
14417         pvInfoList[index-1].time  = 10*time; // centi-sec
14418         if(*sep == '}') *sep = 0; else *--sep = '{';
14419         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14420     }
14421     return sep;
14422 }
14423
14424 void
14425 SendToProgram(message, cps)
14426      char *message;
14427      ChessProgramState *cps;
14428 {
14429     int count, outCount, error;
14430     char buf[MSG_SIZ];
14431
14432     if (cps->pr == NULL) return;
14433     Attention(cps);
14434
14435     if (appData.debugMode) {
14436         TimeMark now;
14437         GetTimeMark(&now);
14438         fprintf(debugFP, "%ld >%-6s: %s",
14439                 SubtractTimeMarks(&now, &programStartTime),
14440                 cps->which, message);
14441     }
14442
14443     count = strlen(message);
14444     outCount = OutputToProcess(cps->pr, message, count, &error);
14445     if (outCount < count && !exiting
14446                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14447       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14448       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14449         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14450             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14451                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14452                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14453                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14454             } else {
14455                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14456                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14457                 gameInfo.result = res;
14458             }
14459             gameInfo.resultDetails = StrSave(buf);
14460         }
14461         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14462         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14463     }
14464 }
14465
14466 void
14467 ReceiveFromProgram(isr, closure, message, count, error)
14468      InputSourceRef isr;
14469      VOIDSTAR closure;
14470      char *message;
14471      int count;
14472      int error;
14473 {
14474     char *end_str;
14475     char buf[MSG_SIZ];
14476     ChessProgramState *cps = (ChessProgramState *)closure;
14477
14478     if (isr != cps->isr) return; /* Killed intentionally */
14479     if (count <= 0) {
14480         if (count == 0) {
14481             RemoveInputSource(cps->isr);
14482             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14483             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14484                     _(cps->which), cps->program);
14485         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14486                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14487                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14488                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14489                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14490                 } else {
14491                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14492                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14493                     gameInfo.result = res;
14494                 }
14495                 gameInfo.resultDetails = StrSave(buf);
14496             }
14497             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14498             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14499         } else {
14500             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14501                     _(cps->which), cps->program);
14502             RemoveInputSource(cps->isr);
14503
14504             /* [AS] Program is misbehaving badly... kill it */
14505             if( count == -2 ) {
14506                 DestroyChildProcess( cps->pr, 9 );
14507                 cps->pr = NoProc;
14508             }
14509
14510             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14511         }
14512         return;
14513     }
14514
14515     if ((end_str = strchr(message, '\r')) != NULL)
14516       *end_str = NULLCHAR;
14517     if ((end_str = strchr(message, '\n')) != NULL)
14518       *end_str = NULLCHAR;
14519
14520     if (appData.debugMode) {
14521         TimeMark now; int print = 1;
14522         char *quote = ""; char c; int i;
14523
14524         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14525                 char start = message[0];
14526                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14527                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14528                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14529                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14530                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14531                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14532                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14533                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14534                    sscanf(message, "hint: %c", &c)!=1 && 
14535                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14536                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14537                     print = (appData.engineComments >= 2);
14538                 }
14539                 message[0] = start; // restore original message
14540         }
14541         if(print) {
14542                 GetTimeMark(&now);
14543                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14544                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14545                         quote,
14546                         message);
14547         }
14548     }
14549
14550     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14551     if (appData.icsEngineAnalyze) {
14552         if (strstr(message, "whisper") != NULL ||
14553              strstr(message, "kibitz") != NULL ||
14554             strstr(message, "tellics") != NULL) return;
14555     }
14556
14557     HandleMachineMove(message, cps);
14558 }
14559
14560
14561 void
14562 SendTimeControl(cps, mps, tc, inc, sd, st)
14563      ChessProgramState *cps;
14564      int mps, inc, sd, st;
14565      long tc;
14566 {
14567     char buf[MSG_SIZ];
14568     int seconds;
14569
14570     if( timeControl_2 > 0 ) {
14571         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14572             tc = timeControl_2;
14573         }
14574     }
14575     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14576     inc /= cps->timeOdds;
14577     st  /= cps->timeOdds;
14578
14579     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14580
14581     if (st > 0) {
14582       /* Set exact time per move, normally using st command */
14583       if (cps->stKludge) {
14584         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14585         seconds = st % 60;
14586         if (seconds == 0) {
14587           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14588         } else {
14589           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14590         }
14591       } else {
14592         snprintf(buf, MSG_SIZ, "st %d\n", st);
14593       }
14594     } else {
14595       /* Set conventional or incremental time control, using level command */
14596       if (seconds == 0) {
14597         /* Note old gnuchess bug -- minutes:seconds used to not work.
14598            Fixed in later versions, but still avoid :seconds
14599            when seconds is 0. */
14600         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14601       } else {
14602         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14603                  seconds, inc/1000.);
14604       }
14605     }
14606     SendToProgram(buf, cps);
14607
14608     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14609     /* Orthogonally, limit search to given depth */
14610     if (sd > 0) {
14611       if (cps->sdKludge) {
14612         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14613       } else {
14614         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14615       }
14616       SendToProgram(buf, cps);
14617     }
14618
14619     if(cps->nps >= 0) { /* [HGM] nps */
14620         if(cps->supportsNPS == FALSE)
14621           cps->nps = -1; // don't use if engine explicitly says not supported!
14622         else {
14623           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14624           SendToProgram(buf, cps);
14625         }
14626     }
14627 }
14628
14629 ChessProgramState *WhitePlayer()
14630 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14631 {
14632     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14633        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14634         return &second;
14635     return &first;
14636 }
14637
14638 void
14639 SendTimeRemaining(cps, machineWhite)
14640      ChessProgramState *cps;
14641      int /*boolean*/ machineWhite;
14642 {
14643     char message[MSG_SIZ];
14644     long time, otime;
14645
14646     /* Note: this routine must be called when the clocks are stopped
14647        or when they have *just* been set or switched; otherwise
14648        it will be off by the time since the current tick started.
14649     */
14650     if (machineWhite) {
14651         time = whiteTimeRemaining / 10;
14652         otime = blackTimeRemaining / 10;
14653     } else {
14654         time = blackTimeRemaining / 10;
14655         otime = whiteTimeRemaining / 10;
14656     }
14657     /* [HGM] translate opponent's time by time-odds factor */
14658     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14659     if (appData.debugMode) {
14660         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14661     }
14662
14663     if (time <= 0) time = 1;
14664     if (otime <= 0) otime = 1;
14665
14666     snprintf(message, MSG_SIZ, "time %ld\n", time);
14667     SendToProgram(message, cps);
14668
14669     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14670     SendToProgram(message, cps);
14671 }
14672
14673 int
14674 BoolFeature(p, name, loc, cps)
14675      char **p;
14676      char *name;
14677      int *loc;
14678      ChessProgramState *cps;
14679 {
14680   char buf[MSG_SIZ];
14681   int len = strlen(name);
14682   int val;
14683
14684   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14685     (*p) += len + 1;
14686     sscanf(*p, "%d", &val);
14687     *loc = (val != 0);
14688     while (**p && **p != ' ')
14689       (*p)++;
14690     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14691     SendToProgram(buf, cps);
14692     return TRUE;
14693   }
14694   return FALSE;
14695 }
14696
14697 int
14698 IntFeature(p, name, loc, cps)
14699      char **p;
14700      char *name;
14701      int *loc;
14702      ChessProgramState *cps;
14703 {
14704   char buf[MSG_SIZ];
14705   int len = strlen(name);
14706   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14707     (*p) += len + 1;
14708     sscanf(*p, "%d", loc);
14709     while (**p && **p != ' ') (*p)++;
14710     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14711     SendToProgram(buf, cps);
14712     return TRUE;
14713   }
14714   return FALSE;
14715 }
14716
14717 int
14718 StringFeature(p, name, loc, cps)
14719      char **p;
14720      char *name;
14721      char loc[];
14722      ChessProgramState *cps;
14723 {
14724   char buf[MSG_SIZ];
14725   int len = strlen(name);
14726   if (strncmp((*p), name, len) == 0
14727       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14728     (*p) += len + 2;
14729     sscanf(*p, "%[^\"]", loc);
14730     while (**p && **p != '\"') (*p)++;
14731     if (**p == '\"') (*p)++;
14732     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14733     SendToProgram(buf, cps);
14734     return TRUE;
14735   }
14736   return FALSE;
14737 }
14738
14739 int
14740 ParseOption(Option *opt, ChessProgramState *cps)
14741 // [HGM] options: process the string that defines an engine option, and determine
14742 // name, type, default value, and allowed value range
14743 {
14744         char *p, *q, buf[MSG_SIZ];
14745         int n, min = (-1)<<31, max = 1<<31, def;
14746
14747         if(p = strstr(opt->name, " -spin ")) {
14748             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14749             if(max < min) max = min; // enforce consistency
14750             if(def < min) def = min;
14751             if(def > max) def = max;
14752             opt->value = def;
14753             opt->min = min;
14754             opt->max = max;
14755             opt->type = Spin;
14756         } else if((p = strstr(opt->name, " -slider "))) {
14757             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14758             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14759             if(max < min) max = min; // enforce consistency
14760             if(def < min) def = min;
14761             if(def > max) def = max;
14762             opt->value = def;
14763             opt->min = min;
14764             opt->max = max;
14765             opt->type = Spin; // Slider;
14766         } else if((p = strstr(opt->name, " -string "))) {
14767             opt->textValue = p+9;
14768             opt->type = TextBox;
14769         } else if((p = strstr(opt->name, " -file "))) {
14770             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14771             opt->textValue = p+7;
14772             opt->type = FileName; // FileName;
14773         } else if((p = strstr(opt->name, " -path "))) {
14774             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14775             opt->textValue = p+7;
14776             opt->type = PathName; // PathName;
14777         } else if(p = strstr(opt->name, " -check ")) {
14778             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14779             opt->value = (def != 0);
14780             opt->type = CheckBox;
14781         } else if(p = strstr(opt->name, " -combo ")) {
14782             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14783             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14784             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14785             opt->value = n = 0;
14786             while(q = StrStr(q, " /// ")) {
14787                 n++; *q = 0;    // count choices, and null-terminate each of them
14788                 q += 5;
14789                 if(*q == '*') { // remember default, which is marked with * prefix
14790                     q++;
14791                     opt->value = n;
14792                 }
14793                 cps->comboList[cps->comboCnt++] = q;
14794             }
14795             cps->comboList[cps->comboCnt++] = NULL;
14796             opt->max = n + 1;
14797             opt->type = ComboBox;
14798         } else if(p = strstr(opt->name, " -button")) {
14799             opt->type = Button;
14800         } else if(p = strstr(opt->name, " -save")) {
14801             opt->type = SaveButton;
14802         } else return FALSE;
14803         *p = 0; // terminate option name
14804         // now look if the command-line options define a setting for this engine option.
14805         if(cps->optionSettings && cps->optionSettings[0])
14806             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14807         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14808           snprintf(buf, MSG_SIZ, "option %s", p);
14809                 if(p = strstr(buf, ",")) *p = 0;
14810                 if(q = strchr(buf, '=')) switch(opt->type) {
14811                     case ComboBox:
14812                         for(n=0; n<opt->max; n++)
14813                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14814                         break;
14815                     case TextBox:
14816                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14817                         break;
14818                     case Spin:
14819                     case CheckBox:
14820                         opt->value = atoi(q+1);
14821                     default:
14822                         break;
14823                 }
14824                 strcat(buf, "\n");
14825                 SendToProgram(buf, cps);
14826         }
14827         return TRUE;
14828 }
14829
14830 void
14831 FeatureDone(cps, val)
14832      ChessProgramState* cps;
14833      int val;
14834 {
14835   DelayedEventCallback cb = GetDelayedEvent();
14836   if ((cb == InitBackEnd3 && cps == &first) ||
14837       (cb == SettingsMenuIfReady && cps == &second) ||
14838       (cb == LoadEngine) ||
14839       (cb == TwoMachinesEventIfReady)) {
14840     CancelDelayedEvent();
14841     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14842   }
14843   cps->initDone = val;
14844 }
14845
14846 /* Parse feature command from engine */
14847 void
14848 ParseFeatures(args, cps)
14849      char* args;
14850      ChessProgramState *cps;
14851 {
14852   char *p = args;
14853   char *q;
14854   int val;
14855   char buf[MSG_SIZ];
14856
14857   for (;;) {
14858     while (*p == ' ') p++;
14859     if (*p == NULLCHAR) return;
14860
14861     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14862     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14863     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14864     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14865     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14866     if (BoolFeature(&p, "reuse", &val, cps)) {
14867       /* Engine can disable reuse, but can't enable it if user said no */
14868       if (!val) cps->reuse = FALSE;
14869       continue;
14870     }
14871     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14872     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14873       if (gameMode == TwoMachinesPlay) {
14874         DisplayTwoMachinesTitle();
14875       } else {
14876         DisplayTitle("");
14877       }
14878       continue;
14879     }
14880     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14881     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14882     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14883     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14884     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14885     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14886     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14887     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14888     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14889     if (IntFeature(&p, "done", &val, cps)) {
14890       FeatureDone(cps, val);
14891       continue;
14892     }
14893     /* Added by Tord: */
14894     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14895     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14896     /* End of additions by Tord */
14897
14898     /* [HGM] added features: */
14899     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14900     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14901     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14902     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14903     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14904     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14905     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14906         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14907           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14908             SendToProgram(buf, cps);
14909             continue;
14910         }
14911         if(cps->nrOptions >= MAX_OPTIONS) {
14912             cps->nrOptions--;
14913             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14914             DisplayError(buf, 0);
14915         }
14916         continue;
14917     }
14918     /* End of additions by HGM */
14919
14920     /* unknown feature: complain and skip */
14921     q = p;
14922     while (*q && *q != '=') q++;
14923     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14924     SendToProgram(buf, cps);
14925     p = q;
14926     if (*p == '=') {
14927       p++;
14928       if (*p == '\"') {
14929         p++;
14930         while (*p && *p != '\"') p++;
14931         if (*p == '\"') p++;
14932       } else {
14933         while (*p && *p != ' ') p++;
14934       }
14935     }
14936   }
14937
14938 }
14939
14940 void
14941 PeriodicUpdatesEvent(newState)
14942      int newState;
14943 {
14944     if (newState == appData.periodicUpdates)
14945       return;
14946
14947     appData.periodicUpdates=newState;
14948
14949     /* Display type changes, so update it now */
14950 //    DisplayAnalysis();
14951
14952     /* Get the ball rolling again... */
14953     if (newState) {
14954         AnalysisPeriodicEvent(1);
14955         StartAnalysisClock();
14956     }
14957 }
14958
14959 void
14960 PonderNextMoveEvent(newState)
14961      int newState;
14962 {
14963     if (newState == appData.ponderNextMove) return;
14964     if (gameMode == EditPosition) EditPositionDone(TRUE);
14965     if (newState) {
14966         SendToProgram("hard\n", &first);
14967         if (gameMode == TwoMachinesPlay) {
14968             SendToProgram("hard\n", &second);
14969         }
14970     } else {
14971         SendToProgram("easy\n", &first);
14972         thinkOutput[0] = NULLCHAR;
14973         if (gameMode == TwoMachinesPlay) {
14974             SendToProgram("easy\n", &second);
14975         }
14976     }
14977     appData.ponderNextMove = newState;
14978 }
14979
14980 void
14981 NewSettingEvent(option, feature, command, value)
14982      char *command;
14983      int option, value, *feature;
14984 {
14985     char buf[MSG_SIZ];
14986
14987     if (gameMode == EditPosition) EditPositionDone(TRUE);
14988     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14989     if(feature == NULL || *feature) SendToProgram(buf, &first);
14990     if (gameMode == TwoMachinesPlay) {
14991         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14992     }
14993 }
14994
14995 void
14996 ShowThinkingEvent()
14997 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14998 {
14999     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15000     int newState = appData.showThinking
15001         // [HGM] thinking: other features now need thinking output as well
15002         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15003
15004     if (oldState == newState) return;
15005     oldState = newState;
15006     if (gameMode == EditPosition) EditPositionDone(TRUE);
15007     if (oldState) {
15008         SendToProgram("post\n", &first);
15009         if (gameMode == TwoMachinesPlay) {
15010             SendToProgram("post\n", &second);
15011         }
15012     } else {
15013         SendToProgram("nopost\n", &first);
15014         thinkOutput[0] = NULLCHAR;
15015         if (gameMode == TwoMachinesPlay) {
15016             SendToProgram("nopost\n", &second);
15017         }
15018     }
15019 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15020 }
15021
15022 void
15023 AskQuestionEvent(title, question, replyPrefix, which)
15024      char *title; char *question; char *replyPrefix; char *which;
15025 {
15026   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15027   if (pr == NoProc) return;
15028   AskQuestion(title, question, replyPrefix, pr);
15029 }
15030
15031 void
15032 TypeInEvent(char firstChar)
15033 {
15034     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15035         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15036         gameMode == AnalyzeMode || gameMode == EditGame || \r
15037         gameMode == EditPosition || gameMode == IcsExamining ||\r
15038         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15039         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15040                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15041                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15042         gameMode == Training) PopUpMoveDialog(firstChar);
15043 }
15044
15045 void
15046 TypeInDoneEvent(char *move)
15047 {
15048         Board board;
15049         int n, fromX, fromY, toX, toY;
15050         char promoChar;
15051         ChessMove moveType;\r
15052
15053         // [HGM] FENedit\r
15054         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15055                 EditPositionPasteFEN(move);\r
15056                 return;\r
15057         }\r
15058         // [HGM] movenum: allow move number to be typed in any mode\r
15059         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15060           ToNrEvent(2*n-1);\r
15061           return;\r
15062         }\r
15063
15064       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15065         gameMode != Training) {\r
15066         DisplayMoveError(_("Displayed move is not current"));\r
15067       } else {\r
15068         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15069           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15070         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15071         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15072           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15073           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15074         } else {\r
15075           DisplayMoveError(_("Could not parse move"));\r
15076         }
15077       }\r
15078 }\r
15079
15080 void
15081 DisplayMove(moveNumber)
15082      int moveNumber;
15083 {
15084     char message[MSG_SIZ];
15085     char res[MSG_SIZ];
15086     char cpThinkOutput[MSG_SIZ];
15087
15088     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15089
15090     if (moveNumber == forwardMostMove - 1 ||
15091         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15092
15093         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15094
15095         if (strchr(cpThinkOutput, '\n')) {
15096             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15097         }
15098     } else {
15099         *cpThinkOutput = NULLCHAR;
15100     }
15101
15102     /* [AS] Hide thinking from human user */
15103     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15104         *cpThinkOutput = NULLCHAR;
15105         if( thinkOutput[0] != NULLCHAR ) {
15106             int i;
15107
15108             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15109                 cpThinkOutput[i] = '.';
15110             }
15111             cpThinkOutput[i] = NULLCHAR;
15112             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15113         }
15114     }
15115
15116     if (moveNumber == forwardMostMove - 1 &&
15117         gameInfo.resultDetails != NULL) {
15118         if (gameInfo.resultDetails[0] == NULLCHAR) {
15119           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15120         } else {
15121           snprintf(res, MSG_SIZ, " {%s} %s",
15122                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15123         }
15124     } else {
15125         res[0] = NULLCHAR;
15126     }
15127
15128     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15129         DisplayMessage(res, cpThinkOutput);
15130     } else {
15131       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15132                 WhiteOnMove(moveNumber) ? " " : ".. ",
15133                 parseList[moveNumber], res);
15134         DisplayMessage(message, cpThinkOutput);
15135     }
15136 }
15137
15138 void
15139 DisplayComment(moveNumber, text)
15140      int moveNumber;
15141      char *text;
15142 {
15143     char title[MSG_SIZ];
15144     char buf[8000]; // comment can be long!
15145     int score, depth;
15146
15147     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15148       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15149     } else {
15150       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15151               WhiteOnMove(moveNumber) ? " " : ".. ",
15152               parseList[moveNumber]);
15153     }
15154     // [HGM] PV info: display PV info together with (or as) comment
15155     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15156       if(text == NULL) text = "";
15157       score = pvInfoList[moveNumber].score;
15158       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15159               depth, (pvInfoList[moveNumber].time+50)/100, text);
15160       text = buf;
15161     }
15162     if (text != NULL && (appData.autoDisplayComment || commentUp))
15163         CommentPopUp(title, text);
15164 }
15165
15166 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15167  * might be busy thinking or pondering.  It can be omitted if your
15168  * gnuchess is configured to stop thinking immediately on any user
15169  * input.  However, that gnuchess feature depends on the FIONREAD
15170  * ioctl, which does not work properly on some flavors of Unix.
15171  */
15172 void
15173 Attention(cps)
15174      ChessProgramState *cps;
15175 {
15176 #if ATTENTION
15177     if (!cps->useSigint) return;
15178     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15179     switch (gameMode) {
15180       case MachinePlaysWhite:
15181       case MachinePlaysBlack:
15182       case TwoMachinesPlay:
15183       case IcsPlayingWhite:
15184       case IcsPlayingBlack:
15185       case AnalyzeMode:
15186       case AnalyzeFile:
15187         /* Skip if we know it isn't thinking */
15188         if (!cps->maybeThinking) return;
15189         if (appData.debugMode)
15190           fprintf(debugFP, "Interrupting %s\n", cps->which);
15191         InterruptChildProcess(cps->pr);
15192         cps->maybeThinking = FALSE;
15193         break;
15194       default:
15195         break;
15196     }
15197 #endif /*ATTENTION*/
15198 }
15199
15200 int
15201 CheckFlags()
15202 {
15203     if (whiteTimeRemaining <= 0) {
15204         if (!whiteFlag) {
15205             whiteFlag = TRUE;
15206             if (appData.icsActive) {
15207                 if (appData.autoCallFlag &&
15208                     gameMode == IcsPlayingBlack && !blackFlag) {
15209                   SendToICS(ics_prefix);
15210                   SendToICS("flag\n");
15211                 }
15212             } else {
15213                 if (blackFlag) {
15214                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15215                 } else {
15216                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15217                     if (appData.autoCallFlag) {
15218                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15219                         return TRUE;
15220                     }
15221                 }
15222             }
15223         }
15224     }
15225     if (blackTimeRemaining <= 0) {
15226         if (!blackFlag) {
15227             blackFlag = TRUE;
15228             if (appData.icsActive) {
15229                 if (appData.autoCallFlag &&
15230                     gameMode == IcsPlayingWhite && !whiteFlag) {
15231                   SendToICS(ics_prefix);
15232                   SendToICS("flag\n");
15233                 }
15234             } else {
15235                 if (whiteFlag) {
15236                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15237                 } else {
15238                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15239                     if (appData.autoCallFlag) {
15240                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15241                         return TRUE;
15242                     }
15243                 }
15244             }
15245         }
15246     }
15247     return FALSE;
15248 }
15249
15250 void
15251 CheckTimeControl()
15252 {
15253     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15254         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15255
15256     /*
15257      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15258      */
15259     if ( !WhiteOnMove(forwardMostMove) ) {
15260         /* White made time control */
15261         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15262         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15263         /* [HGM] time odds: correct new time quota for time odds! */
15264                                             / WhitePlayer()->timeOdds;
15265         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15266     } else {
15267         lastBlack -= blackTimeRemaining;
15268         /* Black made time control */
15269         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15270                                             / WhitePlayer()->other->timeOdds;
15271         lastWhite = whiteTimeRemaining;
15272     }
15273 }
15274
15275 void
15276 DisplayBothClocks()
15277 {
15278     int wom = gameMode == EditPosition ?
15279       !blackPlaysFirst : WhiteOnMove(currentMove);
15280     DisplayWhiteClock(whiteTimeRemaining, wom);
15281     DisplayBlackClock(blackTimeRemaining, !wom);
15282 }
15283
15284
15285 /* Timekeeping seems to be a portability nightmare.  I think everyone
15286    has ftime(), but I'm really not sure, so I'm including some ifdefs
15287    to use other calls if you don't.  Clocks will be less accurate if
15288    you have neither ftime nor gettimeofday.
15289 */
15290
15291 /* VS 2008 requires the #include outside of the function */
15292 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15293 #include <sys/timeb.h>
15294 #endif
15295
15296 /* Get the current time as a TimeMark */
15297 void
15298 GetTimeMark(tm)
15299      TimeMark *tm;
15300 {
15301 #if HAVE_GETTIMEOFDAY
15302
15303     struct timeval timeVal;
15304     struct timezone timeZone;
15305
15306     gettimeofday(&timeVal, &timeZone);
15307     tm->sec = (long) timeVal.tv_sec;
15308     tm->ms = (int) (timeVal.tv_usec / 1000L);
15309
15310 #else /*!HAVE_GETTIMEOFDAY*/
15311 #if HAVE_FTIME
15312
15313 // include <sys/timeb.h> / moved to just above start of function
15314     struct timeb timeB;
15315
15316     ftime(&timeB);
15317     tm->sec = (long) timeB.time;
15318     tm->ms = (int) timeB.millitm;
15319
15320 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15321     tm->sec = (long) time(NULL);
15322     tm->ms = 0;
15323 #endif
15324 #endif
15325 }
15326
15327 /* Return the difference in milliseconds between two
15328    time marks.  We assume the difference will fit in a long!
15329 */
15330 long
15331 SubtractTimeMarks(tm2, tm1)
15332      TimeMark *tm2, *tm1;
15333 {
15334     return 1000L*(tm2->sec - tm1->sec) +
15335            (long) (tm2->ms - tm1->ms);
15336 }
15337
15338
15339 /*
15340  * Code to manage the game clocks.
15341  *
15342  * In tournament play, black starts the clock and then white makes a move.
15343  * We give the human user a slight advantage if he is playing white---the
15344  * clocks don't run until he makes his first move, so it takes zero time.
15345  * Also, we don't account for network lag, so we could get out of sync
15346  * with GNU Chess's clock -- but then, referees are always right.
15347  */
15348
15349 static TimeMark tickStartTM;
15350 static long intendedTickLength;
15351
15352 long
15353 NextTickLength(timeRemaining)
15354      long timeRemaining;
15355 {
15356     long nominalTickLength, nextTickLength;
15357
15358     if (timeRemaining > 0L && timeRemaining <= 10000L)
15359       nominalTickLength = 100L;
15360     else
15361       nominalTickLength = 1000L;
15362     nextTickLength = timeRemaining % nominalTickLength;
15363     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15364
15365     return nextTickLength;
15366 }
15367
15368 /* Adjust clock one minute up or down */
15369 void
15370 AdjustClock(Boolean which, int dir)
15371 {
15372     if(which) blackTimeRemaining += 60000*dir;
15373     else      whiteTimeRemaining += 60000*dir;
15374     DisplayBothClocks();
15375 }
15376
15377 /* Stop clocks and reset to a fresh time control */
15378 void
15379 ResetClocks()
15380 {
15381     (void) StopClockTimer();
15382     if (appData.icsActive) {
15383         whiteTimeRemaining = blackTimeRemaining = 0;
15384     } else if (searchTime) {
15385         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15386         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15387     } else { /* [HGM] correct new time quote for time odds */
15388         whiteTC = blackTC = fullTimeControlString;
15389         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15390         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15391     }
15392     if (whiteFlag || blackFlag) {
15393         DisplayTitle("");
15394         whiteFlag = blackFlag = FALSE;
15395     }
15396     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15397     DisplayBothClocks();
15398 }
15399
15400 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15401
15402 /* Decrement running clock by amount of time that has passed */
15403 void
15404 DecrementClocks()
15405 {
15406     long timeRemaining;
15407     long lastTickLength, fudge;
15408     TimeMark now;
15409
15410     if (!appData.clockMode) return;
15411     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15412
15413     GetTimeMark(&now);
15414
15415     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15416
15417     /* Fudge if we woke up a little too soon */
15418     fudge = intendedTickLength - lastTickLength;
15419     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15420
15421     if (WhiteOnMove(forwardMostMove)) {
15422         if(whiteNPS >= 0) lastTickLength = 0;
15423         timeRemaining = whiteTimeRemaining -= lastTickLength;
15424         if(timeRemaining < 0 && !appData.icsActive) {
15425             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15426             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15427                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15428                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15429             }
15430         }
15431         DisplayWhiteClock(whiteTimeRemaining - fudge,
15432                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15433     } else {
15434         if(blackNPS >= 0) lastTickLength = 0;
15435         timeRemaining = blackTimeRemaining -= lastTickLength;
15436         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15437             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15438             if(suddenDeath) {
15439                 blackStartMove = forwardMostMove;
15440                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15441             }
15442         }
15443         DisplayBlackClock(blackTimeRemaining - fudge,
15444                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15445     }
15446     if (CheckFlags()) return;
15447
15448     tickStartTM = now;
15449     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15450     StartClockTimer(intendedTickLength);
15451
15452     /* if the time remaining has fallen below the alarm threshold, sound the
15453      * alarm. if the alarm has sounded and (due to a takeback or time control
15454      * with increment) the time remaining has increased to a level above the
15455      * threshold, reset the alarm so it can sound again.
15456      */
15457
15458     if (appData.icsActive && appData.icsAlarm) {
15459
15460         /* make sure we are dealing with the user's clock */
15461         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15462                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15463            )) return;
15464
15465         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15466             alarmSounded = FALSE;
15467         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15468             PlayAlarmSound();
15469             alarmSounded = TRUE;
15470         }
15471     }
15472 }
15473
15474
15475 /* A player has just moved, so stop the previously running
15476    clock and (if in clock mode) start the other one.
15477    We redisplay both clocks in case we're in ICS mode, because
15478    ICS gives us an update to both clocks after every move.
15479    Note that this routine is called *after* forwardMostMove
15480    is updated, so the last fractional tick must be subtracted
15481    from the color that is *not* on move now.
15482 */
15483 void
15484 SwitchClocks(int newMoveNr)
15485 {
15486     long lastTickLength;
15487     TimeMark now;
15488     int flagged = FALSE;
15489
15490     GetTimeMark(&now);
15491
15492     if (StopClockTimer() && appData.clockMode) {
15493         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15494         if (!WhiteOnMove(forwardMostMove)) {
15495             if(blackNPS >= 0) lastTickLength = 0;
15496             blackTimeRemaining -= lastTickLength;
15497            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15498 //         if(pvInfoList[forwardMostMove].time == -1)
15499                  pvInfoList[forwardMostMove].time =               // use GUI time
15500                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15501         } else {
15502            if(whiteNPS >= 0) lastTickLength = 0;
15503            whiteTimeRemaining -= lastTickLength;
15504            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15505 //         if(pvInfoList[forwardMostMove].time == -1)
15506                  pvInfoList[forwardMostMove].time =
15507                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15508         }
15509         flagged = CheckFlags();
15510     }
15511     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15512     CheckTimeControl();
15513
15514     if (flagged || !appData.clockMode) return;
15515
15516     switch (gameMode) {
15517       case MachinePlaysBlack:
15518       case MachinePlaysWhite:
15519       case BeginningOfGame:
15520         if (pausing) return;
15521         break;
15522
15523       case EditGame:
15524       case PlayFromGameFile:
15525       case IcsExamining:
15526         return;
15527
15528       default:
15529         break;
15530     }
15531
15532     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15533         if(WhiteOnMove(forwardMostMove))
15534              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15535         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15536     }
15537
15538     tickStartTM = now;
15539     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15540       whiteTimeRemaining : blackTimeRemaining);
15541     StartClockTimer(intendedTickLength);
15542 }
15543
15544
15545 /* Stop both clocks */
15546 void
15547 StopClocks()
15548 {
15549     long lastTickLength;
15550     TimeMark now;
15551
15552     if (!StopClockTimer()) return;
15553     if (!appData.clockMode) return;
15554
15555     GetTimeMark(&now);
15556
15557     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15558     if (WhiteOnMove(forwardMostMove)) {
15559         if(whiteNPS >= 0) lastTickLength = 0;
15560         whiteTimeRemaining -= lastTickLength;
15561         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15562     } else {
15563         if(blackNPS >= 0) lastTickLength = 0;
15564         blackTimeRemaining -= lastTickLength;
15565         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15566     }
15567     CheckFlags();
15568 }
15569
15570 /* Start clock of player on move.  Time may have been reset, so
15571    if clock is already running, stop and restart it. */
15572 void
15573 StartClocks()
15574 {
15575     (void) StopClockTimer(); /* in case it was running already */
15576     DisplayBothClocks();
15577     if (CheckFlags()) return;
15578
15579     if (!appData.clockMode) return;
15580     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15581
15582     GetTimeMark(&tickStartTM);
15583     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15584       whiteTimeRemaining : blackTimeRemaining);
15585
15586    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15587     whiteNPS = blackNPS = -1;
15588     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15589        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15590         whiteNPS = first.nps;
15591     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15592        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15593         blackNPS = first.nps;
15594     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15595         whiteNPS = second.nps;
15596     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15597         blackNPS = second.nps;
15598     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15599
15600     StartClockTimer(intendedTickLength);
15601 }
15602
15603 char *
15604 TimeString(ms)
15605      long ms;
15606 {
15607     long second, minute, hour, day;
15608     char *sign = "";
15609     static char buf[32];
15610
15611     if (ms > 0 && ms <= 9900) {
15612       /* convert milliseconds to tenths, rounding up */
15613       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15614
15615       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15616       return buf;
15617     }
15618
15619     /* convert milliseconds to seconds, rounding up */
15620     /* use floating point to avoid strangeness of integer division
15621        with negative dividends on many machines */
15622     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15623
15624     if (second < 0) {
15625         sign = "-";
15626         second = -second;
15627     }
15628
15629     day = second / (60 * 60 * 24);
15630     second = second % (60 * 60 * 24);
15631     hour = second / (60 * 60);
15632     second = second % (60 * 60);
15633     minute = second / 60;
15634     second = second % 60;
15635
15636     if (day > 0)
15637       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15638               sign, day, hour, minute, second);
15639     else if (hour > 0)
15640       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15641     else
15642       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15643
15644     return buf;
15645 }
15646
15647
15648 /*
15649  * This is necessary because some C libraries aren't ANSI C compliant yet.
15650  */
15651 char *
15652 StrStr(string, match)
15653      char *string, *match;
15654 {
15655     int i, length;
15656
15657     length = strlen(match);
15658
15659     for (i = strlen(string) - length; i >= 0; i--, string++)
15660       if (!strncmp(match, string, length))
15661         return string;
15662
15663     return NULL;
15664 }
15665
15666 char *
15667 StrCaseStr(string, match)
15668      char *string, *match;
15669 {
15670     int i, j, length;
15671
15672     length = strlen(match);
15673
15674     for (i = strlen(string) - length; i >= 0; i--, string++) {
15675         for (j = 0; j < length; j++) {
15676             if (ToLower(match[j]) != ToLower(string[j]))
15677               break;
15678         }
15679         if (j == length) return string;
15680     }
15681
15682     return NULL;
15683 }
15684
15685 #ifndef _amigados
15686 int
15687 StrCaseCmp(s1, s2)
15688      char *s1, *s2;
15689 {
15690     char c1, c2;
15691
15692     for (;;) {
15693         c1 = ToLower(*s1++);
15694         c2 = ToLower(*s2++);
15695         if (c1 > c2) return 1;
15696         if (c1 < c2) return -1;
15697         if (c1 == NULLCHAR) return 0;
15698     }
15699 }
15700
15701
15702 int
15703 ToLower(c)
15704      int c;
15705 {
15706     return isupper(c) ? tolower(c) : c;
15707 }
15708
15709
15710 int
15711 ToUpper(c)
15712      int c;
15713 {
15714     return islower(c) ? toupper(c) : c;
15715 }
15716 #endif /* !_amigados    */
15717
15718 char *
15719 StrSave(s)
15720      char *s;
15721 {
15722   char *ret;
15723
15724   if ((ret = (char *) malloc(strlen(s) + 1)))
15725     {
15726       safeStrCpy(ret, s, strlen(s)+1);
15727     }
15728   return ret;
15729 }
15730
15731 char *
15732 StrSavePtr(s, savePtr)
15733      char *s, **savePtr;
15734 {
15735     if (*savePtr) {
15736         free(*savePtr);
15737     }
15738     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15739       safeStrCpy(*savePtr, s, strlen(s)+1);
15740     }
15741     return(*savePtr);
15742 }
15743
15744 char *
15745 PGNDate()
15746 {
15747     time_t clock;
15748     struct tm *tm;
15749     char buf[MSG_SIZ];
15750
15751     clock = time((time_t *)NULL);
15752     tm = localtime(&clock);
15753     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15754             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15755     return StrSave(buf);
15756 }
15757
15758
15759 char *
15760 PositionToFEN(move, overrideCastling)
15761      int move;
15762      char *overrideCastling;
15763 {
15764     int i, j, fromX, fromY, toX, toY;
15765     int whiteToPlay;
15766     char buf[128];
15767     char *p, *q;
15768     int emptycount;
15769     ChessSquare piece;
15770
15771     whiteToPlay = (gameMode == EditPosition) ?
15772       !blackPlaysFirst : (move % 2 == 0);
15773     p = buf;
15774
15775     /* Piece placement data */
15776     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15777         emptycount = 0;
15778         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15779             if (boards[move][i][j] == EmptySquare) {
15780                 emptycount++;
15781             } else { ChessSquare piece = boards[move][i][j];
15782                 if (emptycount > 0) {
15783                     if(emptycount<10) /* [HGM] can be >= 10 */
15784                         *p++ = '0' + emptycount;
15785                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15786                     emptycount = 0;
15787                 }
15788                 if(PieceToChar(piece) == '+') {
15789                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15790                     *p++ = '+';
15791                     piece = (ChessSquare)(DEMOTED piece);
15792                 }
15793                 *p++ = PieceToChar(piece);
15794                 if(p[-1] == '~') {
15795                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15796                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15797                     *p++ = '~';
15798                 }
15799             }
15800         }
15801         if (emptycount > 0) {
15802             if(emptycount<10) /* [HGM] can be >= 10 */
15803                 *p++ = '0' + emptycount;
15804             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15805             emptycount = 0;
15806         }
15807         *p++ = '/';
15808     }
15809     *(p - 1) = ' ';
15810
15811     /* [HGM] print Crazyhouse or Shogi holdings */
15812     if( gameInfo.holdingsWidth ) {
15813         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15814         q = p;
15815         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15816             piece = boards[move][i][BOARD_WIDTH-1];
15817             if( piece != EmptySquare )
15818               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15819                   *p++ = PieceToChar(piece);
15820         }
15821         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15822             piece = boards[move][BOARD_HEIGHT-i-1][0];
15823             if( piece != EmptySquare )
15824               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15825                   *p++ = PieceToChar(piece);
15826         }
15827
15828         if( q == p ) *p++ = '-';
15829         *p++ = ']';
15830         *p++ = ' ';
15831     }
15832
15833     /* Active color */
15834     *p++ = whiteToPlay ? 'w' : 'b';
15835     *p++ = ' ';
15836
15837   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15838     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15839   } else {
15840   if(nrCastlingRights) {
15841      q = p;
15842      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15843        /* [HGM] write directly from rights */
15844            if(boards[move][CASTLING][2] != NoRights &&
15845               boards[move][CASTLING][0] != NoRights   )
15846                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15847            if(boards[move][CASTLING][2] != NoRights &&
15848               boards[move][CASTLING][1] != NoRights   )
15849                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15850            if(boards[move][CASTLING][5] != NoRights &&
15851               boards[move][CASTLING][3] != NoRights   )
15852                 *p++ = boards[move][CASTLING][3] + AAA;
15853            if(boards[move][CASTLING][5] != NoRights &&
15854               boards[move][CASTLING][4] != NoRights   )
15855                 *p++ = boards[move][CASTLING][4] + AAA;
15856      } else {
15857
15858         /* [HGM] write true castling rights */
15859         if( nrCastlingRights == 6 ) {
15860             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15861                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15862             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15863                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15864             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15865                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15866             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15867                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15868         }
15869      }
15870      if (q == p) *p++ = '-'; /* No castling rights */
15871      *p++ = ' ';
15872   }
15873
15874   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15875      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15876     /* En passant target square */
15877     if (move > backwardMostMove) {
15878         fromX = moveList[move - 1][0] - AAA;
15879         fromY = moveList[move - 1][1] - ONE;
15880         toX = moveList[move - 1][2] - AAA;
15881         toY = moveList[move - 1][3] - ONE;
15882         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15883             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15884             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15885             fromX == toX) {
15886             /* 2-square pawn move just happened */
15887             *p++ = toX + AAA;
15888             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15889         } else {
15890             *p++ = '-';
15891         }
15892     } else if(move == backwardMostMove) {
15893         // [HGM] perhaps we should always do it like this, and forget the above?
15894         if((signed char)boards[move][EP_STATUS] >= 0) {
15895             *p++ = boards[move][EP_STATUS] + AAA;
15896             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15897         } else {
15898             *p++ = '-';
15899         }
15900     } else {
15901         *p++ = '-';
15902     }
15903     *p++ = ' ';
15904   }
15905   }
15906
15907     /* [HGM] find reversible plies */
15908     {   int i = 0, j=move;
15909
15910         if (appData.debugMode) { int k;
15911             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15912             for(k=backwardMostMove; k<=forwardMostMove; k++)
15913                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15914
15915         }
15916
15917         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15918         if( j == backwardMostMove ) i += initialRulePlies;
15919         sprintf(p, "%d ", i);
15920         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15921     }
15922     /* Fullmove number */
15923     sprintf(p, "%d", (move / 2) + 1);
15924
15925     return StrSave(buf);
15926 }
15927
15928 Boolean
15929 ParseFEN(board, blackPlaysFirst, fen)
15930     Board board;
15931      int *blackPlaysFirst;
15932      char *fen;
15933 {
15934     int i, j;
15935     char *p, c;
15936     int emptycount;
15937     ChessSquare piece;
15938
15939     p = fen;
15940
15941     /* [HGM] by default clear Crazyhouse holdings, if present */
15942     if(gameInfo.holdingsWidth) {
15943        for(i=0; i<BOARD_HEIGHT; i++) {
15944            board[i][0]             = EmptySquare; /* black holdings */
15945            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15946            board[i][1]             = (ChessSquare) 0; /* black counts */
15947            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15948        }
15949     }
15950
15951     /* Piece placement data */
15952     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15953         j = 0;
15954         for (;;) {
15955             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15956                 if (*p == '/') p++;
15957                 emptycount = gameInfo.boardWidth - j;
15958                 while (emptycount--)
15959                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15960                 break;
15961 #if(BOARD_FILES >= 10)
15962             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15963                 p++; emptycount=10;
15964                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15965                 while (emptycount--)
15966                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15967 #endif
15968             } else if (isdigit(*p)) {
15969                 emptycount = *p++ - '0';
15970                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15971                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15972                 while (emptycount--)
15973                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15974             } else if (*p == '+' || isalpha(*p)) {
15975                 if (j >= gameInfo.boardWidth) return FALSE;
15976                 if(*p=='+') {
15977                     piece = CharToPiece(*++p);
15978                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15979                     piece = (ChessSquare) (PROMOTED piece ); p++;
15980                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15981                 } else piece = CharToPiece(*p++);
15982
15983                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15984                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15985                     piece = (ChessSquare) (PROMOTED piece);
15986                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15987                     p++;
15988                 }
15989                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15990             } else {
15991                 return FALSE;
15992             }
15993         }
15994     }
15995     while (*p == '/' || *p == ' ') p++;
15996
15997     /* [HGM] look for Crazyhouse holdings here */
15998     while(*p==' ') p++;
15999     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16000         if(*p == '[') p++;
16001         if(*p == '-' ) p++; /* empty holdings */ else {
16002             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16003             /* if we would allow FEN reading to set board size, we would   */
16004             /* have to add holdings and shift the board read so far here   */
16005             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16006                 p++;
16007                 if((int) piece >= (int) BlackPawn ) {
16008                     i = (int)piece - (int)BlackPawn;
16009                     i = PieceToNumber((ChessSquare)i);
16010                     if( i >= gameInfo.holdingsSize ) return FALSE;
16011                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16012                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16013                 } else {
16014                     i = (int)piece - (int)WhitePawn;
16015                     i = PieceToNumber((ChessSquare)i);
16016                     if( i >= gameInfo.holdingsSize ) return FALSE;
16017                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16018                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16019                 }
16020             }
16021         }
16022         if(*p == ']') p++;
16023     }
16024
16025     while(*p == ' ') p++;
16026
16027     /* Active color */
16028     c = *p++;
16029     if(appData.colorNickNames) {
16030       if( c == appData.colorNickNames[0] ) c = 'w'; else
16031       if( c == appData.colorNickNames[1] ) c = 'b';
16032     }
16033     switch (c) {
16034       case 'w':
16035         *blackPlaysFirst = FALSE;
16036         break;
16037       case 'b':
16038         *blackPlaysFirst = TRUE;
16039         break;
16040       default:
16041         return FALSE;
16042     }
16043
16044     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16045     /* return the extra info in global variiables             */
16046
16047     /* set defaults in case FEN is incomplete */
16048     board[EP_STATUS] = EP_UNKNOWN;
16049     for(i=0; i<nrCastlingRights; i++ ) {
16050         board[CASTLING][i] =
16051             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16052     }   /* assume possible unless obviously impossible */
16053     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16054     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16055     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16056                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16057     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16058     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16059     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16060                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16061     FENrulePlies = 0;
16062
16063     while(*p==' ') p++;
16064     if(nrCastlingRights) {
16065       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16066           /* castling indicator present, so default becomes no castlings */
16067           for(i=0; i<nrCastlingRights; i++ ) {
16068                  board[CASTLING][i] = NoRights;
16069           }
16070       }
16071       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16072              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16073              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16074              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16075         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16076
16077         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16078             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16079             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16080         }
16081         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16082             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16083         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16084                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16085         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16086                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16087         switch(c) {
16088           case'K':
16089               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16090               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16091               board[CASTLING][2] = whiteKingFile;
16092               break;
16093           case'Q':
16094               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16095               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16096               board[CASTLING][2] = whiteKingFile;
16097               break;
16098           case'k':
16099               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16100               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16101               board[CASTLING][5] = blackKingFile;
16102               break;
16103           case'q':
16104               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16105               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16106               board[CASTLING][5] = blackKingFile;
16107           case '-':
16108               break;
16109           default: /* FRC castlings */
16110               if(c >= 'a') { /* black rights */
16111                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16112                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16113                   if(i == BOARD_RGHT) break;
16114                   board[CASTLING][5] = i;
16115                   c -= AAA;
16116                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16117                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16118                   if(c > i)
16119                       board[CASTLING][3] = c;
16120                   else
16121                       board[CASTLING][4] = c;
16122               } else { /* white rights */
16123                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16124                     if(board[0][i] == WhiteKing) break;
16125                   if(i == BOARD_RGHT) break;
16126                   board[CASTLING][2] = i;
16127                   c -= AAA - 'a' + 'A';
16128                   if(board[0][c] >= WhiteKing) break;
16129                   if(c > i)
16130                       board[CASTLING][0] = c;
16131                   else
16132                       board[CASTLING][1] = c;
16133               }
16134         }
16135       }
16136       for(i=0; i<nrCastlingRights; i++)
16137         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16138     if (appData.debugMode) {
16139         fprintf(debugFP, "FEN castling rights:");
16140         for(i=0; i<nrCastlingRights; i++)
16141         fprintf(debugFP, " %d", board[CASTLING][i]);
16142         fprintf(debugFP, "\n");
16143     }
16144
16145       while(*p==' ') p++;
16146     }
16147
16148     /* read e.p. field in games that know e.p. capture */
16149     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16150        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16151       if(*p=='-') {
16152         p++; board[EP_STATUS] = EP_NONE;
16153       } else {
16154          char c = *p++ - AAA;
16155
16156          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16157          if(*p >= '0' && *p <='9') p++;
16158          board[EP_STATUS] = c;
16159       }
16160     }
16161
16162
16163     if(sscanf(p, "%d", &i) == 1) {
16164         FENrulePlies = i; /* 50-move ply counter */
16165         /* (The move number is still ignored)    */
16166     }
16167
16168     return TRUE;
16169 }
16170
16171 void
16172 EditPositionPasteFEN(char *fen)
16173 {
16174   if (fen != NULL) {
16175     Board initial_position;
16176
16177     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16178       DisplayError(_("Bad FEN position in clipboard"), 0);
16179       return ;
16180     } else {
16181       int savedBlackPlaysFirst = blackPlaysFirst;
16182       EditPositionEvent();
16183       blackPlaysFirst = savedBlackPlaysFirst;
16184       CopyBoard(boards[0], initial_position);
16185       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16186       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16187       DisplayBothClocks();
16188       DrawPosition(FALSE, boards[currentMove]);
16189     }
16190   }
16191 }
16192
16193 static char cseq[12] = "\\   ";
16194
16195 Boolean set_cont_sequence(char *new_seq)
16196 {
16197     int len;
16198     Boolean ret;
16199
16200     // handle bad attempts to set the sequence
16201         if (!new_seq)
16202                 return 0; // acceptable error - no debug
16203
16204     len = strlen(new_seq);
16205     ret = (len > 0) && (len < sizeof(cseq));
16206     if (ret)
16207       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16208     else if (appData.debugMode)
16209       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16210     return ret;
16211 }
16212
16213 /*
16214     reformat a source message so words don't cross the width boundary.  internal
16215     newlines are not removed.  returns the wrapped size (no null character unless
16216     included in source message).  If dest is NULL, only calculate the size required
16217     for the dest buffer.  lp argument indicats line position upon entry, and it's
16218     passed back upon exit.
16219 */
16220 int wrap(char *dest, char *src, int count, int width, int *lp)
16221 {
16222     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16223
16224     cseq_len = strlen(cseq);
16225     old_line = line = *lp;
16226     ansi = len = clen = 0;
16227
16228     for (i=0; i < count; i++)
16229     {
16230         if (src[i] == '\033')
16231             ansi = 1;
16232
16233         // if we hit the width, back up
16234         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16235         {
16236             // store i & len in case the word is too long
16237             old_i = i, old_len = len;
16238
16239             // find the end of the last word
16240             while (i && src[i] != ' ' && src[i] != '\n')
16241             {
16242                 i--;
16243                 len--;
16244             }
16245
16246             // word too long?  restore i & len before splitting it
16247             if ((old_i-i+clen) >= width)
16248             {
16249                 i = old_i;
16250                 len = old_len;
16251             }
16252
16253             // extra space?
16254             if (i && src[i-1] == ' ')
16255                 len--;
16256
16257             if (src[i] != ' ' && src[i] != '\n')
16258             {
16259                 i--;
16260                 if (len)
16261                     len--;
16262             }
16263
16264             // now append the newline and continuation sequence
16265             if (dest)
16266                 dest[len] = '\n';
16267             len++;
16268             if (dest)
16269                 strncpy(dest+len, cseq, cseq_len);
16270             len += cseq_len;
16271             line = cseq_len;
16272             clen = cseq_len;
16273             continue;
16274         }
16275
16276         if (dest)
16277             dest[len] = src[i];
16278         len++;
16279         if (!ansi)
16280             line++;
16281         if (src[i] == '\n')
16282             line = 0;
16283         if (src[i] == 'm')
16284             ansi = 0;
16285     }
16286     if (dest && appData.debugMode)
16287     {
16288         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16289             count, width, line, len, *lp);
16290         show_bytes(debugFP, src, count);
16291         fprintf(debugFP, "\ndest: ");
16292         show_bytes(debugFP, dest, len);
16293         fprintf(debugFP, "\n");
16294     }
16295     *lp = dest ? line : old_line;
16296
16297     return len;
16298 }
16299
16300 // [HGM] vari: routines for shelving variations
16301
16302 void
16303 PushInner(int firstMove, int lastMove)
16304 {
16305         int i, j, nrMoves = lastMove - firstMove;
16306
16307         // push current tail of game on stack
16308         savedResult[storedGames] = gameInfo.result;
16309         savedDetails[storedGames] = gameInfo.resultDetails;
16310         gameInfo.resultDetails = NULL;
16311         savedFirst[storedGames] = firstMove;
16312         savedLast [storedGames] = lastMove;
16313         savedFramePtr[storedGames] = framePtr;
16314         framePtr -= nrMoves; // reserve space for the boards
16315         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16316             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16317             for(j=0; j<MOVE_LEN; j++)
16318                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16319             for(j=0; j<2*MOVE_LEN; j++)
16320                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16321             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16322             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16323             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16324             pvInfoList[firstMove+i-1].depth = 0;
16325             commentList[framePtr+i] = commentList[firstMove+i];
16326             commentList[firstMove+i] = NULL;
16327         }
16328
16329         storedGames++;
16330         forwardMostMove = firstMove; // truncate game so we can start variation
16331 }
16332
16333 void
16334 PushTail(int firstMove, int lastMove)
16335 {
16336         if(appData.icsActive) { // only in local mode
16337                 forwardMostMove = currentMove; // mimic old ICS behavior
16338                 return;
16339         }
16340         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16341
16342         PushInner(firstMove, lastMove);
16343         if(storedGames == 1) GreyRevert(FALSE);
16344 }
16345
16346 void
16347 PopInner(Boolean annotate)
16348 {
16349         int i, j, nrMoves;
16350         char buf[8000], moveBuf[20];
16351
16352         storedGames--;
16353         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16354         nrMoves = savedLast[storedGames] - currentMove;
16355         if(annotate) {
16356                 int cnt = 10;
16357                 if(!WhiteOnMove(currentMove))
16358                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16359                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16360                 for(i=currentMove; i<forwardMostMove; i++) {
16361                         if(WhiteOnMove(i))
16362                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16363                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16364                         strcat(buf, moveBuf);
16365                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16366                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16367                 }
16368                 strcat(buf, ")");
16369         }
16370         for(i=1; i<=nrMoves; i++) { // copy last variation back
16371             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16372             for(j=0; j<MOVE_LEN; j++)
16373                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16374             for(j=0; j<2*MOVE_LEN; j++)
16375                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16376             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16377             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16378             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16379             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16380             commentList[currentMove+i] = commentList[framePtr+i];
16381             commentList[framePtr+i] = NULL;
16382         }
16383         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16384         framePtr = savedFramePtr[storedGames];
16385         gameInfo.result = savedResult[storedGames];
16386         if(gameInfo.resultDetails != NULL) {
16387             free(gameInfo.resultDetails);
16388       }
16389         gameInfo.resultDetails = savedDetails[storedGames];
16390         forwardMostMove = currentMove + nrMoves;
16391 }
16392
16393 Boolean
16394 PopTail(Boolean annotate)
16395 {
16396         if(appData.icsActive) return FALSE; // only in local mode
16397         if(!storedGames) return FALSE; // sanity
16398         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16399
16400         PopInner(annotate);
16401
16402         if(storedGames == 0) GreyRevert(TRUE);
16403         return TRUE;
16404 }
16405
16406 void
16407 CleanupTail()
16408 {       // remove all shelved variations
16409         int i;
16410         for(i=0; i<storedGames; i++) {
16411             if(savedDetails[i])
16412                 free(savedDetails[i]);
16413             savedDetails[i] = NULL;
16414         }
16415         for(i=framePtr; i<MAX_MOVES; i++) {
16416                 if(commentList[i]) free(commentList[i]);
16417                 commentList[i] = NULL;
16418         }
16419         framePtr = MAX_MOVES-1;
16420         storedGames = 0;
16421 }
16422
16423 void
16424 LoadVariation(int index, char *text)
16425 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16426         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16427         int level = 0, move;
16428
16429         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16430         // first find outermost bracketing variation
16431         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16432             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16433                 if(*p == '{') wait = '}'; else
16434                 if(*p == '[') wait = ']'; else
16435                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16436                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16437             }
16438             if(*p == wait) wait = NULLCHAR; // closing ]} found
16439             p++;
16440         }
16441         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16442         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16443         end[1] = NULLCHAR; // clip off comment beyond variation
16444         ToNrEvent(currentMove-1);
16445         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16446         // kludge: use ParsePV() to append variation to game
16447         move = currentMove;
16448         ParsePV(start, TRUE, TRUE);
16449         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16450         ClearPremoveHighlights();
16451         CommentPopDown();
16452         ToNrEvent(currentMove+1);
16453 }
16454