Fix clock mode in tourney starting from -ncp mode
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237
238 #ifdef WIN32
239        extern void ConsoleCreate();
240 #endif
241
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
245
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 Boolean abortMatch;
254
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 int endPV = -1;
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
262 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
266 Boolean partnerUp;
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
278 int chattingPartner;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
284
285 /* States for ics_getting_history */
286 #define H_FALSE 0
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
292
293 /* whosays values for GameEnds */
294 #define GE_ICS 0
295 #define GE_ENGINE 1
296 #define GE_PLAYER 2
297 #define GE_FILE 3
298 #define GE_XBOARD 4
299 #define GE_ENGINE1 5
300 #define GE_ENGINE2 6
301
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
304
305 /* Different types of move when calling RegisterMove */
306 #define CMAIL_MOVE   0
307 #define CMAIL_RESIGN 1
308 #define CMAIL_DRAW   2
309 #define CMAIL_ACCEPT 3
310
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
315
316 /* Telnet protocol constants */
317 #define TN_WILL 0373
318 #define TN_WONT 0374
319 #define TN_DO   0375
320 #define TN_DONT 0376
321 #define TN_IAC  0377
322 #define TN_ECHO 0001
323 #define TN_SGA  0003
324 #define TN_PORT 23
325
326 char*
327 safeStrCpy( char *dst, const char *src, size_t count )
328 { // [HGM] made safe
329   int i;
330   assert( dst != NULL );
331   assert( src != NULL );
332   assert( count > 0 );
333
334   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335   if(  i == count && dst[count-1] != NULLCHAR)
336     {
337       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338       if(appData.debugMode)
339       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
340     }
341
342   return dst;
343 }
344
345 /* Some compiler can't cast u64 to double
346  * This function do the job for us:
347
348  * We use the highest bit for cast, this only
349  * works if the highest bit is not
350  * in use (This should not happen)
351  *
352  * We used this for all compiler
353  */
354 double
355 u64ToDouble(u64 value)
356 {
357   double r;
358   u64 tmp = value & u64Const(0x7fffffffffffffff);
359   r = (double)(s64)tmp;
360   if (value & u64Const(0x8000000000000000))
361        r +=  9.2233720368547758080e18; /* 2^63 */
362  return r;
363 }
364
365 /* Fake up flags for now, as we aren't keeping track of castling
366    availability yet. [HGM] Change of logic: the flag now only
367    indicates the type of castlings allowed by the rule of the game.
368    The actual rights themselves are maintained in the array
369    castlingRights, as part of the game history, and are not probed
370    by this function.
371  */
372 int
373 PosFlags(index)
374 {
375   int flags = F_ALL_CASTLE_OK;
376   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377   switch (gameInfo.variant) {
378   case VariantSuicide:
379     flags &= ~F_ALL_CASTLE_OK;
380   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381     flags |= F_IGNORE_CHECK;
382   case VariantLosers:
383     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384     break;
385   case VariantAtomic:
386     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387     break;
388   case VariantKriegspiel:
389     flags |= F_KRIEGSPIEL_CAPTURE;
390     break;
391   case VariantCapaRandom:
392   case VariantFischeRandom:
393     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394   case VariantNoCastle:
395   case VariantShatranj:
396   case VariantCourier:
397   case VariantMakruk:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 int shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating(str)
650   char *str;
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine(ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions(ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740 "first",
741 "second"
742 };
743
744 void
745 InitEngine(ChessProgramState *cps, int n)
746 {   // [HGM] all engine initialiation put in a function that does one engine
747
748     ClearOptions(cps);
749
750     cps->which = engineNames[n];
751     cps->maybeThinking = FALSE;
752     cps->pr = NoProc;
753     cps->isr = NULL;
754     cps->sendTime = 2;
755     cps->sendDrawOffers = 1;
756
757     cps->program = appData.chessProgram[n];
758     cps->host = appData.host[n];
759     cps->dir = appData.directory[n];
760     cps->initString = appData.engInitString[n];
761     cps->computerString = appData.computerString[n];
762     cps->useSigint  = TRUE;
763     cps->useSigterm = TRUE;
764     cps->reuse = appData.reuse[n];
765     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
766     cps->useSetboard = FALSE;
767     cps->useSAN = FALSE;
768     cps->usePing = FALSE;
769     cps->lastPing = 0;
770     cps->lastPong = 0;
771     cps->usePlayother = FALSE;
772     cps->useColors = TRUE;
773     cps->useUsermove = FALSE;
774     cps->sendICS = FALSE;
775     cps->sendName = appData.icsActive;
776     cps->sdKludge = FALSE;
777     cps->stKludge = FALSE;
778     TidyProgramName(cps->program, cps->host, cps->tidy);
779     cps->matchWins = 0;
780     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781     cps->analysisSupport = 2; /* detect */
782     cps->analyzing = FALSE;
783     cps->initDone = FALSE;
784
785     /* New features added by Tord: */
786     cps->useFEN960 = FALSE;
787     cps->useOOCastle = TRUE;
788     /* End of new features added by Tord. */
789     cps->fenOverride  = appData.fenOverride[n];
790
791     /* [HGM] time odds: set factor for each machine */
792     cps->timeOdds  = appData.timeOdds[n];
793
794     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795     cps->accumulateTC = appData.accumulateTC[n];
796     cps->maxNrOfSessions = 1;
797
798     /* [HGM] debug */
799     cps->debug = FALSE;
800
801     cps->supportsNPS = UNKNOWN;
802     cps->memSize = FALSE;
803     cps->maxCores = FALSE;
804     cps->egtFormats[0] = NULLCHAR;
805
806     /* [HGM] options */
807     cps->optionSettings  = appData.engOptions[n];
808
809     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810     cps->isUCI = appData.isUCI[n]; /* [AS] */
811     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
812
813     if (appData.protocolVersion[n] > PROTOVER
814         || appData.protocolVersion[n] < 1)
815       {
816         char buf[MSG_SIZ];
817         int len;
818
819         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820                        appData.protocolVersion[n]);
821         if( (len > MSG_SIZ) && appData.debugMode )
822           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
823
824         DisplayFatalError(buf, 0, 2);
825       }
826     else
827       {
828         cps->protocolVersion = appData.protocolVersion[n];
829       }
830
831     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
832 }
833
834 ChessProgramState *savCps;
835
836 void
837 LoadEngine()
838 {
839     int i;
840     if(WaitForEngine(savCps, LoadEngine)) return;
841     CommonEngineInit(); // recalculate time odds
842     if(gameInfo.variant != StringToVariant(appData.variant)) {
843         // we changed variant when loading the engine; this forces us to reset
844         Reset(TRUE, savCps != &first);
845         EditGameEvent(); // for consistency with other path, as Reset changes mode
846     }
847     InitChessProgram(savCps, FALSE);
848     SendToProgram("force\n", savCps);
849     DisplayMessage("", "");
850     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852     ThawUI();
853     SetGNUMode();
854 }
855
856 void
857 ReplaceEngine(ChessProgramState *cps, int n)
858 {
859     EditGameEvent();
860     UnloadEngine(cps);
861     appData.noChessProgram = FALSE;
862     appData.clockMode = TRUE;
863     InitEngine(cps, n);
864     if(n) return; // only startup first engine immediately; second can wait
865     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
866     LoadEngine();
867 }
868
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871
872 static char resetOptions[] = 
873         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900     if(params[0]) {
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
974
975     ClearProgramStats();
976     programStats.ok_to_send = 1;
977     programStats.seen_stat = 0;
978
979     /*
980      * Initialize game list
981      */
982     ListNew(&gameList);
983
984
985     /*
986      * Internet chess server status
987      */
988     if (appData.icsActive) {
989         appData.matchMode = FALSE;
990         appData.matchGames = 0;
991 #if ZIPPY
992         appData.noChessProgram = !appData.zippyPlay;
993 #else
994         appData.zippyPlay = FALSE;
995         appData.zippyTalk = FALSE;
996         appData.noChessProgram = TRUE;
997 #endif
998         if (*appData.icsHelper != NULLCHAR) {
999             appData.useTelnet = TRUE;
1000             appData.telnetProgram = appData.icsHelper;
1001         }
1002     } else {
1003         appData.zippyTalk = appData.zippyPlay = FALSE;
1004     }
1005
1006     /* [AS] Initialize pv info list [HGM] and game state */
1007     {
1008         int i, j;
1009
1010         for( i=0; i<=framePtr; i++ ) {
1011             pvInfoList[i].depth = -1;
1012             boards[i][EP_STATUS] = EP_NONE;
1013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014         }
1015     }
1016
1017     InitTimeControls();
1018
1019     /* [AS] Adjudication threshold */
1020     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1021
1022     InitEngine(&first, 0);
1023     InitEngine(&second, 1);
1024     CommonEngineInit();
1025
1026     pairing.which = "pairing"; // pairing engine
1027     pairing.pr = NoProc;
1028     pairing.isr = NULL;
1029     pairing.program = appData.pairingEngine;
1030     pairing.host = "localhost";
1031     pairing.dir = ".";
1032
1033     if (appData.icsActive) {
1034         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1035     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036         appData.clockMode = FALSE;
1037         first.sendTime = second.sendTime = 0;
1038     }
1039
1040 #if ZIPPY
1041     /* Override some settings from environment variables, for backward
1042        compatibility.  Unfortunately it's not feasible to have the env
1043        vars just set defaults, at least in xboard.  Ugh.
1044     */
1045     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046       ZippyInit();
1047     }
1048 #endif
1049
1050     if (!appData.icsActive) {
1051       char buf[MSG_SIZ];
1052       int len;
1053
1054       /* Check for variants that are supported only in ICS mode,
1055          or not at all.  Some that are accepted here nevertheless
1056          have bugs; see comments below.
1057       */
1058       VariantClass variant = StringToVariant(appData.variant);
1059       switch (variant) {
1060       case VariantBughouse:     /* need four players and two boards */
1061       case VariantKriegspiel:   /* need to hide pieces and move details */
1062         /* case VariantFischeRandom: (Fabien: moved below) */
1063         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064         if( (len > MSG_SIZ) && appData.debugMode )
1065           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1066
1067         DisplayFatalError(buf, 0, 2);
1068         return;
1069
1070       case VariantUnknown:
1071       case VariantLoadable:
1072       case Variant29:
1073       case Variant30:
1074       case Variant31:
1075       case Variant32:
1076       case Variant33:
1077       case Variant34:
1078       case Variant35:
1079       case Variant36:
1080       default:
1081         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082         if( (len > MSG_SIZ) && appData.debugMode )
1083           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1084
1085         DisplayFatalError(buf, 0, 2);
1086         return;
1087
1088       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1089       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1090       case VariantGothic:     /* [HGM] should work */
1091       case VariantCapablanca: /* [HGM] should work */
1092       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1093       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1094       case VariantKnightmate: /* [HGM] should work */
1095       case VariantCylinder:   /* [HGM] untested */
1096       case VariantFalcon:     /* [HGM] untested */
1097       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098                                  offboard interposition not understood */
1099       case VariantNormal:     /* definitely works! */
1100       case VariantWildCastle: /* pieces not automatically shuffled */
1101       case VariantNoCastle:   /* pieces not automatically shuffled */
1102       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103       case VariantLosers:     /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantSuicide:    /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantGiveaway:   /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantTwoKings:   /* should work */
1110       case VariantAtomic:     /* should work except for win condition */
1111       case Variant3Check:     /* should work except for win condition */
1112       case VariantShatranj:   /* should work except for all win conditions */
1113       case VariantMakruk:     /* should work except for daw countdown */
1114       case VariantBerolina:   /* might work if TestLegality is off */
1115       case VariantCapaRandom: /* should work */
1116       case VariantJanus:      /* should work */
1117       case VariantSuper:      /* experimental */
1118       case VariantGreat:      /* experimental, requires legality testing to be off */
1119       case VariantSChess:     /* S-Chess, should work */
1120       case VariantSpartan:    /* should work */
1121         break;
1122       }
1123     }
1124
1125 }
1126
1127 int NextIntegerFromString( char ** str, long * value )
1128 {
1129     int result = -1;
1130     char * s = *str;
1131
1132     while( *s == ' ' || *s == '\t' ) {
1133         s++;
1134     }
1135
1136     *value = 0;
1137
1138     if( *s >= '0' && *s <= '9' ) {
1139         while( *s >= '0' && *s <= '9' ) {
1140             *value = *value * 10 + (*s - '0');
1141             s++;
1142         }
1143
1144         result = 0;
1145     }
1146
1147     *str = s;
1148
1149     return result;
1150 }
1151
1152 int NextTimeControlFromString( char ** str, long * value )
1153 {
1154     long temp;
1155     int result = NextIntegerFromString( str, &temp );
1156
1157     if( result == 0 ) {
1158         *value = temp * 60; /* Minutes */
1159         if( **str == ':' ) {
1160             (*str)++;
1161             result = NextIntegerFromString( str, &temp );
1162             *value += temp; /* Seconds */
1163         }
1164     }
1165
1166     return result;
1167 }
1168
1169 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1170 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1171     int result = -1, type = 0; long temp, temp2;
1172
1173     if(**str != ':') return -1; // old params remain in force!
1174     (*str)++;
1175     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1176     if( NextIntegerFromString( str, &temp ) ) return -1;
1177     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1178
1179     if(**str != '/') {
1180         /* time only: incremental or sudden-death time control */
1181         if(**str == '+') { /* increment follows; read it */
1182             (*str)++;
1183             if(**str == '!') type = *(*str)++; // Bronstein TC
1184             if(result = NextIntegerFromString( str, &temp2)) return -1;
1185             *inc = temp2 * 1000;
1186             if(**str == '.') { // read fraction of increment
1187                 char *start = ++(*str);
1188                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1189                 temp2 *= 1000;
1190                 while(start++ < *str) temp2 /= 10;
1191                 *inc += temp2;
1192             }
1193         } else *inc = 0;
1194         *moves = 0; *tc = temp * 1000; *incType = type;
1195         return 0;
1196     }
1197
1198     (*str)++; /* classical time control */
1199     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1200
1201     if(result == 0) {
1202         *moves = temp;
1203         *tc    = temp2 * 1000;
1204         *inc   = 0;
1205         *incType = type;
1206     }
1207     return result;
1208 }
1209
1210 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1211 {   /* [HGM] get time to add from the multi-session time-control string */
1212     int incType, moves=1; /* kludge to force reading of first session */
1213     long time, increment;
1214     char *s = tcString;
1215
1216     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1217     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1218     do {
1219         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1220         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1221         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1222         if(movenr == -1) return time;    /* last move before new session     */
1223         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1224         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1225         if(!moves) return increment;     /* current session is incremental   */
1226         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1227     } while(movenr >= -1);               /* try again for next session       */
1228
1229     return 0; // no new time quota on this move
1230 }
1231
1232 int
1233 ParseTimeControl(tc, ti, mps)
1234      char *tc;
1235      float ti;
1236      int mps;
1237 {
1238   long tc1;
1239   long tc2;
1240   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1241   int min, sec=0;
1242
1243   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1244   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1245       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1246   if(ti > 0) {
1247
1248     if(mps)
1249       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1250     else 
1251       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1252   } else {
1253     if(mps)
1254       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1255     else 
1256       snprintf(buf, MSG_SIZ, ":%s", mytc);
1257   }
1258   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1259   
1260   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1261     return FALSE;
1262   }
1263
1264   if( *tc == '/' ) {
1265     /* Parse second time control */
1266     tc++;
1267
1268     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1269       return FALSE;
1270     }
1271
1272     if( tc2 == 0 ) {
1273       return FALSE;
1274     }
1275
1276     timeControl_2 = tc2 * 1000;
1277   }
1278   else {
1279     timeControl_2 = 0;
1280   }
1281
1282   if( tc1 == 0 ) {
1283     return FALSE;
1284   }
1285
1286   timeControl = tc1 * 1000;
1287
1288   if (ti >= 0) {
1289     timeIncrement = ti * 1000;  /* convert to ms */
1290     movesPerSession = 0;
1291   } else {
1292     timeIncrement = 0;
1293     movesPerSession = mps;
1294   }
1295   return TRUE;
1296 }
1297
1298 void
1299 InitBackEnd2()
1300 {
1301     if (appData.debugMode) {
1302         fprintf(debugFP, "%s\n", programVersion);
1303     }
1304
1305     set_cont_sequence(appData.wrapContSeq);
1306     if (appData.matchGames > 0) {
1307         appData.matchMode = TRUE;
1308     } else if (appData.matchMode) {
1309         appData.matchGames = 1;
1310     }
1311     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1312         appData.matchGames = appData.sameColorGames;
1313     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1314         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1315         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1316     }
1317     Reset(TRUE, FALSE);
1318     if (appData.noChessProgram || first.protocolVersion == 1) {
1319       InitBackEnd3();
1320     } else {
1321       /* kludge: allow timeout for initial "feature" commands */
1322       FreezeUI();
1323       DisplayMessage("", _("Starting chess program"));
1324       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1325     }
1326 }
1327
1328 int
1329 CalculateIndex(int index, int gameNr)
1330 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1331     int res;
1332     if(index > 0) return index; // fixed nmber
1333     if(index == 0) return 1;
1334     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1335     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1336     return res;
1337 }
1338
1339 int
1340 LoadGameOrPosition(int gameNr)
1341 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1342     if (*appData.loadGameFile != NULLCHAR) {
1343         if (!LoadGameFromFile(appData.loadGameFile,
1344                 CalculateIndex(appData.loadGameIndex, gameNr),
1345                               appData.loadGameFile, FALSE)) {
1346             DisplayFatalError(_("Bad game file"), 0, 1);
1347             return 0;
1348         }
1349     } else if (*appData.loadPositionFile != NULLCHAR) {
1350         if (!LoadPositionFromFile(appData.loadPositionFile,
1351                 CalculateIndex(appData.loadPositionIndex, gameNr),
1352                                   appData.loadPositionFile)) {
1353             DisplayFatalError(_("Bad position file"), 0, 1);
1354             return 0;
1355         }
1356     }
1357     return 1;
1358 }
1359
1360 void
1361 ReserveGame(int gameNr, char resChar)
1362 {
1363     FILE *tf = fopen(appData.tourneyFile, "r+");
1364     char *p, *q, c, buf[MSG_SIZ];
1365     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1366     safeStrCpy(buf, lastMsg, MSG_SIZ);
1367     DisplayMessage(_("Pick new game"), "");
1368     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1369     ParseArgsFromFile(tf);
1370     p = q = appData.results;
1371     if(appData.debugMode) {
1372       char *r = appData.participants;
1373       fprintf(debugFP, "results = '%s'\n", p);
1374       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1375       fprintf(debugFP, "\n");
1376     }
1377     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1378     nextGame = q - p;
1379     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1380     safeStrCpy(q, p, strlen(p) + 2);
1381     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1382     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1383     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1384         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1385         q[nextGame] = '*';
1386     }
1387     fseek(tf, -(strlen(p)+4), SEEK_END);
1388     c = fgetc(tf);
1389     if(c != '"') // depending on DOS or Unix line endings we can be one off
1390          fseek(tf, -(strlen(p)+2), SEEK_END);
1391     else fseek(tf, -(strlen(p)+3), SEEK_END);
1392     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1393     DisplayMessage(buf, "");
1394     free(p); appData.results = q;
1395     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1396        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1397         UnloadEngine(&first);  // next game belongs to other pairing;
1398         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1399     }
1400 }
1401
1402 void
1403 MatchEvent(int mode)
1404 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1405         int dummy;
1406         if(matchMode) { // already in match mode: switch it off
1407             abortMatch = TRUE;
1408             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1409             return;
1410         }
1411 //      if(gameMode != BeginningOfGame) {
1412 //          DisplayError(_("You can only start a match from the initial position."), 0);
1413 //          return;
1414 //      }
1415         abortMatch = FALSE;
1416         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1417         /* Set up machine vs. machine match */
1418         nextGame = 0;
1419         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1420         if(appData.tourneyFile[0]) {
1421             ReserveGame(-1, 0);
1422             if(nextGame > appData.matchGames) {
1423                 char buf[MSG_SIZ];
1424                 if(strchr(appData.results, '*') == NULL) {
1425                     FILE *f;
1426                     appData.tourneyCycles++;
1427                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1428                         fclose(f);
1429                         NextTourneyGame(-1, &dummy);
1430                         ReserveGame(-1, 0);
1431                         if(nextGame <= appData.matchGames) {
1432                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1433                             matchMode = mode;
1434                             ScheduleDelayedEvent(NextMatchGame, 10000);
1435                             return;
1436                         }
1437                     }
1438                 }
1439                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1440                 DisplayError(buf, 0);
1441                 appData.tourneyFile[0] = 0;
1442                 return;
1443             }
1444         } else
1445         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1446             DisplayFatalError(_("Can't have a match with no chess programs"),
1447                               0, 2);
1448             return;
1449         }
1450         matchMode = mode;
1451         matchGame = roundNr = 1;
1452         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1453         NextMatchGame();
1454 }
1455
1456 void
1457 InitBackEnd3 P((void))
1458 {
1459     GameMode initialMode;
1460     char buf[MSG_SIZ];
1461     int err, len;
1462
1463     InitChessProgram(&first, startedFromSetupPosition);
1464
1465     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1466         free(programVersion);
1467         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1468         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1469     }
1470
1471     if (appData.icsActive) {
1472 #ifdef WIN32
1473         /* [DM] Make a console window if needed [HGM] merged ifs */
1474         ConsoleCreate();
1475 #endif
1476         err = establish();
1477         if (err != 0)
1478           {
1479             if (*appData.icsCommPort != NULLCHAR)
1480               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1481                              appData.icsCommPort);
1482             else
1483               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1484                         appData.icsHost, appData.icsPort);
1485
1486             if( (len > MSG_SIZ) && appData.debugMode )
1487               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1488
1489             DisplayFatalError(buf, err, 1);
1490             return;
1491         }
1492         SetICSMode();
1493         telnetISR =
1494           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1495         fromUserISR =
1496           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1497         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1498             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1499     } else if (appData.noChessProgram) {
1500         SetNCPMode();
1501     } else {
1502         SetGNUMode();
1503     }
1504
1505     if (*appData.cmailGameName != NULLCHAR) {
1506         SetCmailMode();
1507         OpenLoopback(&cmailPR);
1508         cmailISR =
1509           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1510     }
1511
1512     ThawUI();
1513     DisplayMessage("", "");
1514     if (StrCaseCmp(appData.initialMode, "") == 0) {
1515       initialMode = BeginningOfGame;
1516       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1517         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1518         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1519         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1520         ModeHighlight();
1521       }
1522     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1523       initialMode = TwoMachinesPlay;
1524     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1525       initialMode = AnalyzeFile;
1526     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1527       initialMode = AnalyzeMode;
1528     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1529       initialMode = MachinePlaysWhite;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1531       initialMode = MachinePlaysBlack;
1532     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1533       initialMode = EditGame;
1534     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1535       initialMode = EditPosition;
1536     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1537       initialMode = Training;
1538     } else {
1539       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1540       if( (len > MSG_SIZ) && appData.debugMode )
1541         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1542
1543       DisplayFatalError(buf, 0, 2);
1544       return;
1545     }
1546
1547     if (appData.matchMode) {
1548         if(appData.tourneyFile[0]) { // start tourney from command line
1549             FILE *f;
1550             if(f = fopen(appData.tourneyFile, "r")) {
1551                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1552                 fclose(f);
1553                 appData.clockMode = TRUE;
1554                 SetGNUMode();
1555             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1556         }
1557         MatchEvent(TRUE);
1558     } else if (*appData.cmailGameName != NULLCHAR) {
1559         /* Set up cmail mode */
1560         ReloadCmailMsgEvent(TRUE);
1561     } else {
1562         /* Set up other modes */
1563         if (initialMode == AnalyzeFile) {
1564           if (*appData.loadGameFile == NULLCHAR) {
1565             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1566             return;
1567           }
1568         }
1569         if (*appData.loadGameFile != NULLCHAR) {
1570             (void) LoadGameFromFile(appData.loadGameFile,
1571                                     appData.loadGameIndex,
1572                                     appData.loadGameFile, TRUE);
1573         } else if (*appData.loadPositionFile != NULLCHAR) {
1574             (void) LoadPositionFromFile(appData.loadPositionFile,
1575                                         appData.loadPositionIndex,
1576                                         appData.loadPositionFile);
1577             /* [HGM] try to make self-starting even after FEN load */
1578             /* to allow automatic setup of fairy variants with wtm */
1579             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1580                 gameMode = BeginningOfGame;
1581                 setboardSpoiledMachineBlack = 1;
1582             }
1583             /* [HGM] loadPos: make that every new game uses the setup */
1584             /* from file as long as we do not switch variant          */
1585             if(!blackPlaysFirst) {
1586                 startedFromPositionFile = TRUE;
1587                 CopyBoard(filePosition, boards[0]);
1588             }
1589         }
1590         if (initialMode == AnalyzeMode) {
1591           if (appData.noChessProgram) {
1592             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1593             return;
1594           }
1595           if (appData.icsActive) {
1596             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1597             return;
1598           }
1599           AnalyzeModeEvent();
1600         } else if (initialMode == AnalyzeFile) {
1601           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1602           ShowThinkingEvent();
1603           AnalyzeFileEvent();
1604           AnalysisPeriodicEvent(1);
1605         } else if (initialMode == MachinePlaysWhite) {
1606           if (appData.noChessProgram) {
1607             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1608                               0, 2);
1609             return;
1610           }
1611           if (appData.icsActive) {
1612             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1613                               0, 2);
1614             return;
1615           }
1616           MachineWhiteEvent();
1617         } else if (initialMode == MachinePlaysBlack) {
1618           if (appData.noChessProgram) {
1619             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1620                               0, 2);
1621             return;
1622           }
1623           if (appData.icsActive) {
1624             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1625                               0, 2);
1626             return;
1627           }
1628           MachineBlackEvent();
1629         } else if (initialMode == TwoMachinesPlay) {
1630           if (appData.noChessProgram) {
1631             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1632                               0, 2);
1633             return;
1634           }
1635           if (appData.icsActive) {
1636             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1637                               0, 2);
1638             return;
1639           }
1640           TwoMachinesEvent();
1641         } else if (initialMode == EditGame) {
1642           EditGameEvent();
1643         } else if (initialMode == EditPosition) {
1644           EditPositionEvent();
1645         } else if (initialMode == Training) {
1646           if (*appData.loadGameFile == NULLCHAR) {
1647             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1648             return;
1649           }
1650           TrainingEvent();
1651         }
1652     }
1653 }
1654
1655 /*
1656  * Establish will establish a contact to a remote host.port.
1657  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1658  *  used to talk to the host.
1659  * Returns 0 if okay, error code if not.
1660  */
1661 int
1662 establish()
1663 {
1664     char buf[MSG_SIZ];
1665
1666     if (*appData.icsCommPort != NULLCHAR) {
1667         /* Talk to the host through a serial comm port */
1668         return OpenCommPort(appData.icsCommPort, &icsPR);
1669
1670     } else if (*appData.gateway != NULLCHAR) {
1671         if (*appData.remoteShell == NULLCHAR) {
1672             /* Use the rcmd protocol to run telnet program on a gateway host */
1673             snprintf(buf, sizeof(buf), "%s %s %s",
1674                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1675             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1676
1677         } else {
1678             /* Use the rsh program to run telnet program on a gateway host */
1679             if (*appData.remoteUser == NULLCHAR) {
1680                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1681                         appData.gateway, appData.telnetProgram,
1682                         appData.icsHost, appData.icsPort);
1683             } else {
1684                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1685                         appData.remoteShell, appData.gateway,
1686                         appData.remoteUser, appData.telnetProgram,
1687                         appData.icsHost, appData.icsPort);
1688             }
1689             return StartChildProcess(buf, "", &icsPR);
1690
1691         }
1692     } else if (appData.useTelnet) {
1693         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1694
1695     } else {
1696         /* TCP socket interface differs somewhat between
1697            Unix and NT; handle details in the front end.
1698            */
1699         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1700     }
1701 }
1702
1703 void EscapeExpand(char *p, char *q)
1704 {       // [HGM] initstring: routine to shape up string arguments
1705         while(*p++ = *q++) if(p[-1] == '\\')
1706             switch(*q++) {
1707                 case 'n': p[-1] = '\n'; break;
1708                 case 'r': p[-1] = '\r'; break;
1709                 case 't': p[-1] = '\t'; break;
1710                 case '\\': p[-1] = '\\'; break;
1711                 case 0: *p = 0; return;
1712                 default: p[-1] = q[-1]; break;
1713             }
1714 }
1715
1716 void
1717 show_bytes(fp, buf, count)
1718      FILE *fp;
1719      char *buf;
1720      int count;
1721 {
1722     while (count--) {
1723         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1724             fprintf(fp, "\\%03o", *buf & 0xff);
1725         } else {
1726             putc(*buf, fp);
1727         }
1728         buf++;
1729     }
1730     fflush(fp);
1731 }
1732
1733 /* Returns an errno value */
1734 int
1735 OutputMaybeTelnet(pr, message, count, outError)
1736      ProcRef pr;
1737      char *message;
1738      int count;
1739      int *outError;
1740 {
1741     char buf[8192], *p, *q, *buflim;
1742     int left, newcount, outcount;
1743
1744     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1745         *appData.gateway != NULLCHAR) {
1746         if (appData.debugMode) {
1747             fprintf(debugFP, ">ICS: ");
1748             show_bytes(debugFP, message, count);
1749             fprintf(debugFP, "\n");
1750         }
1751         return OutputToProcess(pr, message, count, outError);
1752     }
1753
1754     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1755     p = message;
1756     q = buf;
1757     left = count;
1758     newcount = 0;
1759     while (left) {
1760         if (q >= buflim) {
1761             if (appData.debugMode) {
1762                 fprintf(debugFP, ">ICS: ");
1763                 show_bytes(debugFP, buf, newcount);
1764                 fprintf(debugFP, "\n");
1765             }
1766             outcount = OutputToProcess(pr, buf, newcount, outError);
1767             if (outcount < newcount) return -1; /* to be sure */
1768             q = buf;
1769             newcount = 0;
1770         }
1771         if (*p == '\n') {
1772             *q++ = '\r';
1773             newcount++;
1774         } else if (((unsigned char) *p) == TN_IAC) {
1775             *q++ = (char) TN_IAC;
1776             newcount ++;
1777         }
1778         *q++ = *p++;
1779         newcount++;
1780         left--;
1781     }
1782     if (appData.debugMode) {
1783         fprintf(debugFP, ">ICS: ");
1784         show_bytes(debugFP, buf, newcount);
1785         fprintf(debugFP, "\n");
1786     }
1787     outcount = OutputToProcess(pr, buf, newcount, outError);
1788     if (outcount < newcount) return -1; /* to be sure */
1789     return count;
1790 }
1791
1792 void
1793 read_from_player(isr, closure, message, count, error)
1794      InputSourceRef isr;
1795      VOIDSTAR closure;
1796      char *message;
1797      int count;
1798      int error;
1799 {
1800     int outError, outCount;
1801     static int gotEof = 0;
1802
1803     /* Pass data read from player on to ICS */
1804     if (count > 0) {
1805         gotEof = 0;
1806         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1807         if (outCount < count) {
1808             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1809         }
1810     } else if (count < 0) {
1811         RemoveInputSource(isr);
1812         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1813     } else if (gotEof++ > 0) {
1814         RemoveInputSource(isr);
1815         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1816     }
1817 }
1818
1819 void
1820 KeepAlive()
1821 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1822     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1823     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1824     SendToICS("date\n");
1825     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1826 }
1827
1828 /* added routine for printf style output to ics */
1829 void ics_printf(char *format, ...)
1830 {
1831     char buffer[MSG_SIZ];
1832     va_list args;
1833
1834     va_start(args, format);
1835     vsnprintf(buffer, sizeof(buffer), format, args);
1836     buffer[sizeof(buffer)-1] = '\0';
1837     SendToICS(buffer);
1838     va_end(args);
1839 }
1840
1841 void
1842 SendToICS(s)
1843      char *s;
1844 {
1845     int count, outCount, outError;
1846
1847     if (icsPR == NULL) return;
1848
1849     count = strlen(s);
1850     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1851     if (outCount < count) {
1852         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853     }
1854 }
1855
1856 /* This is used for sending logon scripts to the ICS. Sending
1857    without a delay causes problems when using timestamp on ICC
1858    (at least on my machine). */
1859 void
1860 SendToICSDelayed(s,msdelay)
1861      char *s;
1862      long msdelay;
1863 {
1864     int count, outCount, outError;
1865
1866     if (icsPR == NULL) return;
1867
1868     count = strlen(s);
1869     if (appData.debugMode) {
1870         fprintf(debugFP, ">ICS: ");
1871         show_bytes(debugFP, s, count);
1872         fprintf(debugFP, "\n");
1873     }
1874     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1875                                       msdelay);
1876     if (outCount < count) {
1877         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1878     }
1879 }
1880
1881
1882 /* Remove all highlighting escape sequences in s
1883    Also deletes any suffix starting with '('
1884    */
1885 char *
1886 StripHighlightAndTitle(s)
1887      char *s;
1888 {
1889     static char retbuf[MSG_SIZ];
1890     char *p = retbuf;
1891
1892     while (*s != NULLCHAR) {
1893         while (*s == '\033') {
1894             while (*s != NULLCHAR && !isalpha(*s)) s++;
1895             if (*s != NULLCHAR) s++;
1896         }
1897         while (*s != NULLCHAR && *s != '\033') {
1898             if (*s == '(' || *s == '[') {
1899                 *p = NULLCHAR;
1900                 return retbuf;
1901             }
1902             *p++ = *s++;
1903         }
1904     }
1905     *p = NULLCHAR;
1906     return retbuf;
1907 }
1908
1909 /* Remove all highlighting escape sequences in s */
1910 char *
1911 StripHighlight(s)
1912      char *s;
1913 {
1914     static char retbuf[MSG_SIZ];
1915     char *p = retbuf;
1916
1917     while (*s != NULLCHAR) {
1918         while (*s == '\033') {
1919             while (*s != NULLCHAR && !isalpha(*s)) s++;
1920             if (*s != NULLCHAR) s++;
1921         }
1922         while (*s != NULLCHAR && *s != '\033') {
1923             *p++ = *s++;
1924         }
1925     }
1926     *p = NULLCHAR;
1927     return retbuf;
1928 }
1929
1930 char *variantNames[] = VARIANT_NAMES;
1931 char *
1932 VariantName(v)
1933      VariantClass v;
1934 {
1935     return variantNames[v];
1936 }
1937
1938
1939 /* Identify a variant from the strings the chess servers use or the
1940    PGN Variant tag names we use. */
1941 VariantClass
1942 StringToVariant(e)
1943      char *e;
1944 {
1945     char *p;
1946     int wnum = -1;
1947     VariantClass v = VariantNormal;
1948     int i, found = FALSE;
1949     char buf[MSG_SIZ];
1950     int len;
1951
1952     if (!e) return v;
1953
1954     /* [HGM] skip over optional board-size prefixes */
1955     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1956         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1957         while( *e++ != '_');
1958     }
1959
1960     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1961         v = VariantNormal;
1962         found = TRUE;
1963     } else
1964     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1965       if (StrCaseStr(e, variantNames[i])) {
1966         v = (VariantClass) i;
1967         found = TRUE;
1968         break;
1969       }
1970     }
1971
1972     if (!found) {
1973       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1974           || StrCaseStr(e, "wild/fr")
1975           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1976         v = VariantFischeRandom;
1977       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1978                  (i = 1, p = StrCaseStr(e, "w"))) {
1979         p += i;
1980         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1981         if (isdigit(*p)) {
1982           wnum = atoi(p);
1983         } else {
1984           wnum = -1;
1985         }
1986         switch (wnum) {
1987         case 0: /* FICS only, actually */
1988         case 1:
1989           /* Castling legal even if K starts on d-file */
1990           v = VariantWildCastle;
1991           break;
1992         case 2:
1993         case 3:
1994         case 4:
1995           /* Castling illegal even if K & R happen to start in
1996              normal positions. */
1997           v = VariantNoCastle;
1998           break;
1999         case 5:
2000         case 7:
2001         case 8:
2002         case 10:
2003         case 11:
2004         case 12:
2005         case 13:
2006         case 14:
2007         case 15:
2008         case 18:
2009         case 19:
2010           /* Castling legal iff K & R start in normal positions */
2011           v = VariantNormal;
2012           break;
2013         case 6:
2014         case 20:
2015         case 21:
2016           /* Special wilds for position setup; unclear what to do here */
2017           v = VariantLoadable;
2018           break;
2019         case 9:
2020           /* Bizarre ICC game */
2021           v = VariantTwoKings;
2022           break;
2023         case 16:
2024           v = VariantKriegspiel;
2025           break;
2026         case 17:
2027           v = VariantLosers;
2028           break;
2029         case 22:
2030           v = VariantFischeRandom;
2031           break;
2032         case 23:
2033           v = VariantCrazyhouse;
2034           break;
2035         case 24:
2036           v = VariantBughouse;
2037           break;
2038         case 25:
2039           v = Variant3Check;
2040           break;
2041         case 26:
2042           /* Not quite the same as FICS suicide! */
2043           v = VariantGiveaway;
2044           break;
2045         case 27:
2046           v = VariantAtomic;
2047           break;
2048         case 28:
2049           v = VariantShatranj;
2050           break;
2051
2052         /* Temporary names for future ICC types.  The name *will* change in
2053            the next xboard/WinBoard release after ICC defines it. */
2054         case 29:
2055           v = Variant29;
2056           break;
2057         case 30:
2058           v = Variant30;
2059           break;
2060         case 31:
2061           v = Variant31;
2062           break;
2063         case 32:
2064           v = Variant32;
2065           break;
2066         case 33:
2067           v = Variant33;
2068           break;
2069         case 34:
2070           v = Variant34;
2071           break;
2072         case 35:
2073           v = Variant35;
2074           break;
2075         case 36:
2076           v = Variant36;
2077           break;
2078         case 37:
2079           v = VariantShogi;
2080           break;
2081         case 38:
2082           v = VariantXiangqi;
2083           break;
2084         case 39:
2085           v = VariantCourier;
2086           break;
2087         case 40:
2088           v = VariantGothic;
2089           break;
2090         case 41:
2091           v = VariantCapablanca;
2092           break;
2093         case 42:
2094           v = VariantKnightmate;
2095           break;
2096         case 43:
2097           v = VariantFairy;
2098           break;
2099         case 44:
2100           v = VariantCylinder;
2101           break;
2102         case 45:
2103           v = VariantFalcon;
2104           break;
2105         case 46:
2106           v = VariantCapaRandom;
2107           break;
2108         case 47:
2109           v = VariantBerolina;
2110           break;
2111         case 48:
2112           v = VariantJanus;
2113           break;
2114         case 49:
2115           v = VariantSuper;
2116           break;
2117         case 50:
2118           v = VariantGreat;
2119           break;
2120         case -1:
2121           /* Found "wild" or "w" in the string but no number;
2122              must assume it's normal chess. */
2123           v = VariantNormal;
2124           break;
2125         default:
2126           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2127           if( (len > MSG_SIZ) && appData.debugMode )
2128             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2129
2130           DisplayError(buf, 0);
2131           v = VariantUnknown;
2132           break;
2133         }
2134       }
2135     }
2136     if (appData.debugMode) {
2137       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2138               e, wnum, VariantName(v));
2139     }
2140     return v;
2141 }
2142
2143 static int leftover_start = 0, leftover_len = 0;
2144 char star_match[STAR_MATCH_N][MSG_SIZ];
2145
2146 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2147    advance *index beyond it, and set leftover_start to the new value of
2148    *index; else return FALSE.  If pattern contains the character '*', it
2149    matches any sequence of characters not containing '\r', '\n', or the
2150    character following the '*' (if any), and the matched sequence(s) are
2151    copied into star_match.
2152    */
2153 int
2154 looking_at(buf, index, pattern)
2155      char *buf;
2156      int *index;
2157      char *pattern;
2158 {
2159     char *bufp = &buf[*index], *patternp = pattern;
2160     int star_count = 0;
2161     char *matchp = star_match[0];
2162
2163     for (;;) {
2164         if (*patternp == NULLCHAR) {
2165             *index = leftover_start = bufp - buf;
2166             *matchp = NULLCHAR;
2167             return TRUE;
2168         }
2169         if (*bufp == NULLCHAR) return FALSE;
2170         if (*patternp == '*') {
2171             if (*bufp == *(patternp + 1)) {
2172                 *matchp = NULLCHAR;
2173                 matchp = star_match[++star_count];
2174                 patternp += 2;
2175                 bufp++;
2176                 continue;
2177             } else if (*bufp == '\n' || *bufp == '\r') {
2178                 patternp++;
2179                 if (*patternp == NULLCHAR)
2180                   continue;
2181                 else
2182                   return FALSE;
2183             } else {
2184                 *matchp++ = *bufp++;
2185                 continue;
2186             }
2187         }
2188         if (*patternp != *bufp) return FALSE;
2189         patternp++;
2190         bufp++;
2191     }
2192 }
2193
2194 void
2195 SendToPlayer(data, length)
2196      char *data;
2197      int length;
2198 {
2199     int error, outCount;
2200     outCount = OutputToProcess(NoProc, data, length, &error);
2201     if (outCount < length) {
2202         DisplayFatalError(_("Error writing to display"), error, 1);
2203     }
2204 }
2205
2206 void
2207 PackHolding(packed, holding)
2208      char packed[];
2209      char *holding;
2210 {
2211     char *p = holding;
2212     char *q = packed;
2213     int runlength = 0;
2214     int curr = 9999;
2215     do {
2216         if (*p == curr) {
2217             runlength++;
2218         } else {
2219             switch (runlength) {
2220               case 0:
2221                 break;
2222               case 1:
2223                 *q++ = curr;
2224                 break;
2225               case 2:
2226                 *q++ = curr;
2227                 *q++ = curr;
2228                 break;
2229               default:
2230                 sprintf(q, "%d", runlength);
2231                 while (*q) q++;
2232                 *q++ = curr;
2233                 break;
2234             }
2235             runlength = 1;
2236             curr = *p;
2237         }
2238     } while (*p++);
2239     *q = NULLCHAR;
2240 }
2241
2242 /* Telnet protocol requests from the front end */
2243 void
2244 TelnetRequest(ddww, option)
2245      unsigned char ddww, option;
2246 {
2247     unsigned char msg[3];
2248     int outCount, outError;
2249
2250     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2251
2252     if (appData.debugMode) {
2253         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2254         switch (ddww) {
2255           case TN_DO:
2256             ddwwStr = "DO";
2257             break;
2258           case TN_DONT:
2259             ddwwStr = "DONT";
2260             break;
2261           case TN_WILL:
2262             ddwwStr = "WILL";
2263             break;
2264           case TN_WONT:
2265             ddwwStr = "WONT";
2266             break;
2267           default:
2268             ddwwStr = buf1;
2269             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2270             break;
2271         }
2272         switch (option) {
2273           case TN_ECHO:
2274             optionStr = "ECHO";
2275             break;
2276           default:
2277             optionStr = buf2;
2278             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2279             break;
2280         }
2281         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2282     }
2283     msg[0] = TN_IAC;
2284     msg[1] = ddww;
2285     msg[2] = option;
2286     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2287     if (outCount < 3) {
2288         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2289     }
2290 }
2291
2292 void
2293 DoEcho()
2294 {
2295     if (!appData.icsActive) return;
2296     TelnetRequest(TN_DO, TN_ECHO);
2297 }
2298
2299 void
2300 DontEcho()
2301 {
2302     if (!appData.icsActive) return;
2303     TelnetRequest(TN_DONT, TN_ECHO);
2304 }
2305
2306 void
2307 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2308 {
2309     /* put the holdings sent to us by the server on the board holdings area */
2310     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2311     char p;
2312     ChessSquare piece;
2313
2314     if(gameInfo.holdingsWidth < 2)  return;
2315     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2316         return; // prevent overwriting by pre-board holdings
2317
2318     if( (int)lowestPiece >= BlackPawn ) {
2319         holdingsColumn = 0;
2320         countsColumn = 1;
2321         holdingsStartRow = BOARD_HEIGHT-1;
2322         direction = -1;
2323     } else {
2324         holdingsColumn = BOARD_WIDTH-1;
2325         countsColumn = BOARD_WIDTH-2;
2326         holdingsStartRow = 0;
2327         direction = 1;
2328     }
2329
2330     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2331         board[i][holdingsColumn] = EmptySquare;
2332         board[i][countsColumn]   = (ChessSquare) 0;
2333     }
2334     while( (p=*holdings++) != NULLCHAR ) {
2335         piece = CharToPiece( ToUpper(p) );
2336         if(piece == EmptySquare) continue;
2337         /*j = (int) piece - (int) WhitePawn;*/
2338         j = PieceToNumber(piece);
2339         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2340         if(j < 0) continue;               /* should not happen */
2341         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2342         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2343         board[holdingsStartRow+j*direction][countsColumn]++;
2344     }
2345 }
2346
2347
2348 void
2349 VariantSwitch(Board board, VariantClass newVariant)
2350 {
2351    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2352    static Board oldBoard;
2353
2354    startedFromPositionFile = FALSE;
2355    if(gameInfo.variant == newVariant) return;
2356
2357    /* [HGM] This routine is called each time an assignment is made to
2358     * gameInfo.variant during a game, to make sure the board sizes
2359     * are set to match the new variant. If that means adding or deleting
2360     * holdings, we shift the playing board accordingly
2361     * This kludge is needed because in ICS observe mode, we get boards
2362     * of an ongoing game without knowing the variant, and learn about the
2363     * latter only later. This can be because of the move list we requested,
2364     * in which case the game history is refilled from the beginning anyway,
2365     * but also when receiving holdings of a crazyhouse game. In the latter
2366     * case we want to add those holdings to the already received position.
2367     */
2368
2369
2370    if (appData.debugMode) {
2371      fprintf(debugFP, "Switch board from %s to %s\n",
2372              VariantName(gameInfo.variant), VariantName(newVariant));
2373      setbuf(debugFP, NULL);
2374    }
2375    shuffleOpenings = 0;       /* [HGM] shuffle */
2376    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2377    switch(newVariant)
2378      {
2379      case VariantShogi:
2380        newWidth = 9;  newHeight = 9;
2381        gameInfo.holdingsSize = 7;
2382      case VariantBughouse:
2383      case VariantCrazyhouse:
2384        newHoldingsWidth = 2; break;
2385      case VariantGreat:
2386        newWidth = 10;
2387      case VariantSuper:
2388        newHoldingsWidth = 2;
2389        gameInfo.holdingsSize = 8;
2390        break;
2391      case VariantGothic:
2392      case VariantCapablanca:
2393      case VariantCapaRandom:
2394        newWidth = 10;
2395      default:
2396        newHoldingsWidth = gameInfo.holdingsSize = 0;
2397      };
2398
2399    if(newWidth  != gameInfo.boardWidth  ||
2400       newHeight != gameInfo.boardHeight ||
2401       newHoldingsWidth != gameInfo.holdingsWidth ) {
2402
2403      /* shift position to new playing area, if needed */
2404      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2405        for(i=0; i<BOARD_HEIGHT; i++)
2406          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2407            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2408              board[i][j];
2409        for(i=0; i<newHeight; i++) {
2410          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2411          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2412        }
2413      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2414        for(i=0; i<BOARD_HEIGHT; i++)
2415          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2416            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2417              board[i][j];
2418      }
2419      gameInfo.boardWidth  = newWidth;
2420      gameInfo.boardHeight = newHeight;
2421      gameInfo.holdingsWidth = newHoldingsWidth;
2422      gameInfo.variant = newVariant;
2423      InitDrawingSizes(-2, 0);
2424    } else gameInfo.variant = newVariant;
2425    CopyBoard(oldBoard, board);   // remember correctly formatted board
2426      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2427    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2428 }
2429
2430 static int loggedOn = FALSE;
2431
2432 /*-- Game start info cache: --*/
2433 int gs_gamenum;
2434 char gs_kind[MSG_SIZ];
2435 static char player1Name[128] = "";
2436 static char player2Name[128] = "";
2437 static char cont_seq[] = "\n\\   ";
2438 static int player1Rating = -1;
2439 static int player2Rating = -1;
2440 /*----------------------------*/
2441
2442 ColorClass curColor = ColorNormal;
2443 int suppressKibitz = 0;
2444
2445 // [HGM] seekgraph
2446 Boolean soughtPending = FALSE;
2447 Boolean seekGraphUp;
2448 #define MAX_SEEK_ADS 200
2449 #define SQUARE 0x80
2450 char *seekAdList[MAX_SEEK_ADS];
2451 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2452 float tcList[MAX_SEEK_ADS];
2453 char colorList[MAX_SEEK_ADS];
2454 int nrOfSeekAds = 0;
2455 int minRating = 1010, maxRating = 2800;
2456 int hMargin = 10, vMargin = 20, h, w;
2457 extern int squareSize, lineGap;
2458
2459 void
2460 PlotSeekAd(int i)
2461 {
2462         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2463         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2464         if(r < minRating+100 && r >=0 ) r = minRating+100;
2465         if(r > maxRating) r = maxRating;
2466         if(tc < 1.) tc = 1.;
2467         if(tc > 95.) tc = 95.;
2468         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2469         y = ((double)r - minRating)/(maxRating - minRating)
2470             * (h-vMargin-squareSize/8-1) + vMargin;
2471         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2472         if(strstr(seekAdList[i], " u ")) color = 1;
2473         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2474            !strstr(seekAdList[i], "bullet") &&
2475            !strstr(seekAdList[i], "blitz") &&
2476            !strstr(seekAdList[i], "standard") ) color = 2;
2477         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2478         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2479 }
2480
2481 void
2482 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2483 {
2484         char buf[MSG_SIZ], *ext = "";
2485         VariantClass v = StringToVariant(type);
2486         if(strstr(type, "wild")) {
2487             ext = type + 4; // append wild number
2488             if(v == VariantFischeRandom) type = "chess960"; else
2489             if(v == VariantLoadable) type = "setup"; else
2490             type = VariantName(v);
2491         }
2492         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2493         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2494             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2495             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2496             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2497             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2498             seekNrList[nrOfSeekAds] = nr;
2499             zList[nrOfSeekAds] = 0;
2500             seekAdList[nrOfSeekAds++] = StrSave(buf);
2501             if(plot) PlotSeekAd(nrOfSeekAds-1);
2502         }
2503 }
2504
2505 void
2506 EraseSeekDot(int i)
2507 {
2508     int x = xList[i], y = yList[i], d=squareSize/4, k;
2509     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2510     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2511     // now replot every dot that overlapped
2512     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2513         int xx = xList[k], yy = yList[k];
2514         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2515             DrawSeekDot(xx, yy, colorList[k]);
2516     }
2517 }
2518
2519 void
2520 RemoveSeekAd(int nr)
2521 {
2522         int i;
2523         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2524             EraseSeekDot(i);
2525             if(seekAdList[i]) free(seekAdList[i]);
2526             seekAdList[i] = seekAdList[--nrOfSeekAds];
2527             seekNrList[i] = seekNrList[nrOfSeekAds];
2528             ratingList[i] = ratingList[nrOfSeekAds];
2529             colorList[i]  = colorList[nrOfSeekAds];
2530             tcList[i] = tcList[nrOfSeekAds];
2531             xList[i]  = xList[nrOfSeekAds];
2532             yList[i]  = yList[nrOfSeekAds];
2533             zList[i]  = zList[nrOfSeekAds];
2534             seekAdList[nrOfSeekAds] = NULL;
2535             break;
2536         }
2537 }
2538
2539 Boolean
2540 MatchSoughtLine(char *line)
2541 {
2542     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2543     int nr, base, inc, u=0; char dummy;
2544
2545     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2546        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2547        (u=1) &&
2548        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2549         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2550         // match: compact and save the line
2551         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2552         return TRUE;
2553     }
2554     return FALSE;
2555 }
2556
2557 int
2558 DrawSeekGraph()
2559 {
2560     int i;
2561     if(!seekGraphUp) return FALSE;
2562     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2563     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2564
2565     DrawSeekBackground(0, 0, w, h);
2566     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2567     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2568     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2569         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2570         yy = h-1-yy;
2571         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2572         if(i%500 == 0) {
2573             char buf[MSG_SIZ];
2574             snprintf(buf, MSG_SIZ, "%d", i);
2575             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2576         }
2577     }
2578     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2579     for(i=1; i<100; i+=(i<10?1:5)) {
2580         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2581         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2582         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2583             char buf[MSG_SIZ];
2584             snprintf(buf, MSG_SIZ, "%d", i);
2585             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2586         }
2587     }
2588     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2589     return TRUE;
2590 }
2591
2592 int SeekGraphClick(ClickType click, int x, int y, int moving)
2593 {
2594     static int lastDown = 0, displayed = 0, lastSecond;
2595     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2596         if(click == Release || moving) return FALSE;
2597         nrOfSeekAds = 0;
2598         soughtPending = TRUE;
2599         SendToICS(ics_prefix);
2600         SendToICS("sought\n"); // should this be "sought all"?
2601     } else { // issue challenge based on clicked ad
2602         int dist = 10000; int i, closest = 0, second = 0;
2603         for(i=0; i<nrOfSeekAds; i++) {
2604             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2605             if(d < dist) { dist = d; closest = i; }
2606             second += (d - zList[i] < 120); // count in-range ads
2607             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2608         }
2609         if(dist < 120) {
2610             char buf[MSG_SIZ];
2611             second = (second > 1);
2612             if(displayed != closest || second != lastSecond) {
2613                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2614                 lastSecond = second; displayed = closest;
2615             }
2616             if(click == Press) {
2617                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2618                 lastDown = closest;
2619                 return TRUE;
2620             } // on press 'hit', only show info
2621             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2622             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2623             SendToICS(ics_prefix);
2624             SendToICS(buf);
2625             return TRUE; // let incoming board of started game pop down the graph
2626         } else if(click == Release) { // release 'miss' is ignored
2627             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2628             if(moving == 2) { // right up-click
2629                 nrOfSeekAds = 0; // refresh graph
2630                 soughtPending = TRUE;
2631                 SendToICS(ics_prefix);
2632                 SendToICS("sought\n"); // should this be "sought all"?
2633             }
2634             return TRUE;
2635         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2636         // press miss or release hit 'pop down' seek graph
2637         seekGraphUp = FALSE;
2638         DrawPosition(TRUE, NULL);
2639     }
2640     return TRUE;
2641 }
2642
2643 void
2644 read_from_ics(isr, closure, data, count, error)
2645      InputSourceRef isr;
2646      VOIDSTAR closure;
2647      char *data;
2648      int count;
2649      int error;
2650 {
2651 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2652 #define STARTED_NONE 0
2653 #define STARTED_MOVES 1
2654 #define STARTED_BOARD 2
2655 #define STARTED_OBSERVE 3
2656 #define STARTED_HOLDINGS 4
2657 #define STARTED_CHATTER 5
2658 #define STARTED_COMMENT 6
2659 #define STARTED_MOVES_NOHIDE 7
2660
2661     static int started = STARTED_NONE;
2662     static char parse[20000];
2663     static int parse_pos = 0;
2664     static char buf[BUF_SIZE + 1];
2665     static int firstTime = TRUE, intfSet = FALSE;
2666     static ColorClass prevColor = ColorNormal;
2667     static int savingComment = FALSE;
2668     static int cmatch = 0; // continuation sequence match
2669     char *bp;
2670     char str[MSG_SIZ];
2671     int i, oldi;
2672     int buf_len;
2673     int next_out;
2674     int tkind;
2675     int backup;    /* [DM] For zippy color lines */
2676     char *p;
2677     char talker[MSG_SIZ]; // [HGM] chat
2678     int channel;
2679
2680     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2681
2682     if (appData.debugMode) {
2683       if (!error) {
2684         fprintf(debugFP, "<ICS: ");
2685         show_bytes(debugFP, data, count);
2686         fprintf(debugFP, "\n");
2687       }
2688     }
2689
2690     if (appData.debugMode) { int f = forwardMostMove;
2691         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2692                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2693                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2694     }
2695     if (count > 0) {
2696         /* If last read ended with a partial line that we couldn't parse,
2697            prepend it to the new read and try again. */
2698         if (leftover_len > 0) {
2699             for (i=0; i<leftover_len; i++)
2700               buf[i] = buf[leftover_start + i];
2701         }
2702
2703     /* copy new characters into the buffer */
2704     bp = buf + leftover_len;
2705     buf_len=leftover_len;
2706     for (i=0; i<count; i++)
2707     {
2708         // ignore these
2709         if (data[i] == '\r')
2710             continue;
2711
2712         // join lines split by ICS?
2713         if (!appData.noJoin)
2714         {
2715             /*
2716                 Joining just consists of finding matches against the
2717                 continuation sequence, and discarding that sequence
2718                 if found instead of copying it.  So, until a match
2719                 fails, there's nothing to do since it might be the
2720                 complete sequence, and thus, something we don't want
2721                 copied.
2722             */
2723             if (data[i] == cont_seq[cmatch])
2724             {
2725                 cmatch++;
2726                 if (cmatch == strlen(cont_seq))
2727                 {
2728                     cmatch = 0; // complete match.  just reset the counter
2729
2730                     /*
2731                         it's possible for the ICS to not include the space
2732                         at the end of the last word, making our [correct]
2733                         join operation fuse two separate words.  the server
2734                         does this when the space occurs at the width setting.
2735                     */
2736                     if (!buf_len || buf[buf_len-1] != ' ')
2737                     {
2738                         *bp++ = ' ';
2739                         buf_len++;
2740                     }
2741                 }
2742                 continue;
2743             }
2744             else if (cmatch)
2745             {
2746                 /*
2747                     match failed, so we have to copy what matched before
2748                     falling through and copying this character.  In reality,
2749                     this will only ever be just the newline character, but
2750                     it doesn't hurt to be precise.
2751                 */
2752                 strncpy(bp, cont_seq, cmatch);
2753                 bp += cmatch;
2754                 buf_len += cmatch;
2755                 cmatch = 0;
2756             }
2757         }
2758
2759         // copy this char
2760         *bp++ = data[i];
2761         buf_len++;
2762     }
2763
2764         buf[buf_len] = NULLCHAR;
2765 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2766         next_out = 0;
2767         leftover_start = 0;
2768
2769         i = 0;
2770         while (i < buf_len) {
2771             /* Deal with part of the TELNET option negotiation
2772                protocol.  We refuse to do anything beyond the
2773                defaults, except that we allow the WILL ECHO option,
2774                which ICS uses to turn off password echoing when we are
2775                directly connected to it.  We reject this option
2776                if localLineEditing mode is on (always on in xboard)
2777                and we are talking to port 23, which might be a real
2778                telnet server that will try to keep WILL ECHO on permanently.
2779              */
2780             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2781                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2782                 unsigned char option;
2783                 oldi = i;
2784                 switch ((unsigned char) buf[++i]) {
2785                   case TN_WILL:
2786                     if (appData.debugMode)
2787                       fprintf(debugFP, "\n<WILL ");
2788                     switch (option = (unsigned char) buf[++i]) {
2789                       case TN_ECHO:
2790                         if (appData.debugMode)
2791                           fprintf(debugFP, "ECHO ");
2792                         /* Reply only if this is a change, according
2793                            to the protocol rules. */
2794                         if (remoteEchoOption) break;
2795                         if (appData.localLineEditing &&
2796                             atoi(appData.icsPort) == TN_PORT) {
2797                             TelnetRequest(TN_DONT, TN_ECHO);
2798                         } else {
2799                             EchoOff();
2800                             TelnetRequest(TN_DO, TN_ECHO);
2801                             remoteEchoOption = TRUE;
2802                         }
2803                         break;
2804                       default:
2805                         if (appData.debugMode)
2806                           fprintf(debugFP, "%d ", option);
2807                         /* Whatever this is, we don't want it. */
2808                         TelnetRequest(TN_DONT, option);
2809                         break;
2810                     }
2811                     break;
2812                   case TN_WONT:
2813                     if (appData.debugMode)
2814                       fprintf(debugFP, "\n<WONT ");
2815                     switch (option = (unsigned char) buf[++i]) {
2816                       case TN_ECHO:
2817                         if (appData.debugMode)
2818                           fprintf(debugFP, "ECHO ");
2819                         /* Reply only if this is a change, according
2820                            to the protocol rules. */
2821                         if (!remoteEchoOption) break;
2822                         EchoOn();
2823                         TelnetRequest(TN_DONT, TN_ECHO);
2824                         remoteEchoOption = FALSE;
2825                         break;
2826                       default:
2827                         if (appData.debugMode)
2828                           fprintf(debugFP, "%d ", (unsigned char) option);
2829                         /* Whatever this is, it must already be turned
2830                            off, because we never agree to turn on
2831                            anything non-default, so according to the
2832                            protocol rules, we don't reply. */
2833                         break;
2834                     }
2835                     break;
2836                   case TN_DO:
2837                     if (appData.debugMode)
2838                       fprintf(debugFP, "\n<DO ");
2839                     switch (option = (unsigned char) buf[++i]) {
2840                       default:
2841                         /* Whatever this is, we refuse to do it. */
2842                         if (appData.debugMode)
2843                           fprintf(debugFP, "%d ", option);
2844                         TelnetRequest(TN_WONT, option);
2845                         break;
2846                     }
2847                     break;
2848                   case TN_DONT:
2849                     if (appData.debugMode)
2850                       fprintf(debugFP, "\n<DONT ");
2851                     switch (option = (unsigned char) buf[++i]) {
2852                       default:
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "%d ", option);
2855                         /* Whatever this is, we are already not doing
2856                            it, because we never agree to do anything
2857                            non-default, so according to the protocol
2858                            rules, we don't reply. */
2859                         break;
2860                     }
2861                     break;
2862                   case TN_IAC:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<IAC ");
2865                     /* Doubled IAC; pass it through */
2866                     i--;
2867                     break;
2868                   default:
2869                     if (appData.debugMode)
2870                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2871                     /* Drop all other telnet commands on the floor */
2872                     break;
2873                 }
2874                 if (oldi > next_out)
2875                   SendToPlayer(&buf[next_out], oldi - next_out);
2876                 if (++i > next_out)
2877                   next_out = i;
2878                 continue;
2879             }
2880
2881             /* OK, this at least will *usually* work */
2882             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2883                 loggedOn = TRUE;
2884             }
2885
2886             if (loggedOn && !intfSet) {
2887                 if (ics_type == ICS_ICC) {
2888                   snprintf(str, MSG_SIZ,
2889                           "/set-quietly interface %s\n/set-quietly style 12\n",
2890                           programVersion);
2891                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2892                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2893                 } else if (ics_type == ICS_CHESSNET) {
2894                   snprintf(str, MSG_SIZ, "/style 12\n");
2895                 } else {
2896                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2897                   strcat(str, programVersion);
2898                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2899                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2900                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2901 #ifdef WIN32
2902                   strcat(str, "$iset nohighlight 1\n");
2903 #endif
2904                   strcat(str, "$iset lock 1\n$style 12\n");
2905                 }
2906                 SendToICS(str);
2907                 NotifyFrontendLogin();
2908                 intfSet = TRUE;
2909             }
2910
2911             if (started == STARTED_COMMENT) {
2912                 /* Accumulate characters in comment */
2913                 parse[parse_pos++] = buf[i];
2914                 if (buf[i] == '\n') {
2915                     parse[parse_pos] = NULLCHAR;
2916                     if(chattingPartner>=0) {
2917                         char mess[MSG_SIZ];
2918                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2919                         OutputChatMessage(chattingPartner, mess);
2920                         chattingPartner = -1;
2921                         next_out = i+1; // [HGM] suppress printing in ICS window
2922                     } else
2923                     if(!suppressKibitz) // [HGM] kibitz
2924                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2925                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2926                         int nrDigit = 0, nrAlph = 0, j;
2927                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2928                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2929                         parse[parse_pos] = NULLCHAR;
2930                         // try to be smart: if it does not look like search info, it should go to
2931                         // ICS interaction window after all, not to engine-output window.
2932                         for(j=0; j<parse_pos; j++) { // count letters and digits
2933                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2934                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2935                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2936                         }
2937                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2938                             int depth=0; float score;
2939                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2940                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2941                                 pvInfoList[forwardMostMove-1].depth = depth;
2942                                 pvInfoList[forwardMostMove-1].score = 100*score;
2943                             }
2944                             OutputKibitz(suppressKibitz, parse);
2945                         } else {
2946                             char tmp[MSG_SIZ];
2947                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2948                             SendToPlayer(tmp, strlen(tmp));
2949                         }
2950                         next_out = i+1; // [HGM] suppress printing in ICS window
2951                     }
2952                     started = STARTED_NONE;
2953                 } else {
2954                     /* Don't match patterns against characters in comment */
2955                     i++;
2956                     continue;
2957                 }
2958             }
2959             if (started == STARTED_CHATTER) {
2960                 if (buf[i] != '\n') {
2961                     /* Don't match patterns against characters in chatter */
2962                     i++;
2963                     continue;
2964                 }
2965                 started = STARTED_NONE;
2966                 if(suppressKibitz) next_out = i+1;
2967             }
2968
2969             /* Kludge to deal with rcmd protocol */
2970             if (firstTime && looking_at(buf, &i, "\001*")) {
2971                 DisplayFatalError(&buf[1], 0, 1);
2972                 continue;
2973             } else {
2974                 firstTime = FALSE;
2975             }
2976
2977             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2978                 ics_type = ICS_ICC;
2979                 ics_prefix = "/";
2980                 if (appData.debugMode)
2981                   fprintf(debugFP, "ics_type %d\n", ics_type);
2982                 continue;
2983             }
2984             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2985                 ics_type = ICS_FICS;
2986                 ics_prefix = "$";
2987                 if (appData.debugMode)
2988                   fprintf(debugFP, "ics_type %d\n", ics_type);
2989                 continue;
2990             }
2991             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2992                 ics_type = ICS_CHESSNET;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998
2999             if (!loggedOn &&
3000                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3001                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3002                  looking_at(buf, &i, "will be \"*\""))) {
3003               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3004               continue;
3005             }
3006
3007             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3008               char buf[MSG_SIZ];
3009               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3010               DisplayIcsInteractionTitle(buf);
3011               have_set_title = TRUE;
3012             }
3013
3014             /* skip finger notes */
3015             if (started == STARTED_NONE &&
3016                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3017                  (buf[i] == '1' && buf[i+1] == '0')) &&
3018                 buf[i+2] == ':' && buf[i+3] == ' ') {
3019               started = STARTED_CHATTER;
3020               i += 3;
3021               continue;
3022             }
3023
3024             oldi = i;
3025             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3026             if(appData.seekGraph) {
3027                 if(soughtPending && MatchSoughtLine(buf+i)) {
3028                     i = strstr(buf+i, "rated") - buf;
3029                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3030                     next_out = leftover_start = i;
3031                     started = STARTED_CHATTER;
3032                     suppressKibitz = TRUE;
3033                     continue;
3034                 }
3035                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3036                         && looking_at(buf, &i, "* ads displayed")) {
3037                     soughtPending = FALSE;
3038                     seekGraphUp = TRUE;
3039                     DrawSeekGraph();
3040                     continue;
3041                 }
3042                 if(appData.autoRefresh) {
3043                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3044                         int s = (ics_type == ICS_ICC); // ICC format differs
3045                         if(seekGraphUp)
3046                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3047                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3048                         looking_at(buf, &i, "*% "); // eat prompt
3049                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3050                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3051                         next_out = i; // suppress
3052                         continue;
3053                     }
3054                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3055                         char *p = star_match[0];
3056                         while(*p) {
3057                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3058                             while(*p && *p++ != ' '); // next
3059                         }
3060                         looking_at(buf, &i, "*% "); // eat prompt
3061                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3062                         next_out = i;
3063                         continue;
3064                     }
3065                 }
3066             }
3067
3068             /* skip formula vars */
3069             if (started == STARTED_NONE &&
3070                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3071               started = STARTED_CHATTER;
3072               i += 3;
3073               continue;
3074             }
3075
3076             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3077             if (appData.autoKibitz && started == STARTED_NONE &&
3078                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3079                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3080                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3081                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3082                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3083                         suppressKibitz = TRUE;
3084                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3085                         next_out = i;
3086                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3087                                 && (gameMode == IcsPlayingWhite)) ||
3088                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3089                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3090                             started = STARTED_CHATTER; // own kibitz we simply discard
3091                         else {
3092                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3093                             parse_pos = 0; parse[0] = NULLCHAR;
3094                             savingComment = TRUE;
3095                             suppressKibitz = gameMode != IcsObserving ? 2 :
3096                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3097                         }
3098                         continue;
3099                 } else
3100                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3101                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3102                          && atoi(star_match[0])) {
3103                     // suppress the acknowledgements of our own autoKibitz
3104                     char *p;
3105                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3106                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3107                     SendToPlayer(star_match[0], strlen(star_match[0]));
3108                     if(looking_at(buf, &i, "*% ")) // eat prompt
3109                         suppressKibitz = FALSE;
3110                     next_out = i;
3111                     continue;
3112                 }
3113             } // [HGM] kibitz: end of patch
3114
3115             // [HGM] chat: intercept tells by users for which we have an open chat window
3116             channel = -1;
3117             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3118                                            looking_at(buf, &i, "* whispers:") ||
3119                                            looking_at(buf, &i, "* kibitzes:") ||
3120                                            looking_at(buf, &i, "* shouts:") ||
3121                                            looking_at(buf, &i, "* c-shouts:") ||
3122                                            looking_at(buf, &i, "--> * ") ||
3123                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3124                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3125                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3126                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3127                 int p;
3128                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3129                 chattingPartner = -1;
3130
3131                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3132                 for(p=0; p<MAX_CHAT; p++) {
3133                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3134                     talker[0] = '['; strcat(talker, "] ");
3135                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3136                     chattingPartner = p; break;
3137                     }
3138                 } else
3139                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3140                 for(p=0; p<MAX_CHAT; p++) {
3141                     if(!strcmp("kibitzes", chatPartner[p])) {
3142                         talker[0] = '['; strcat(talker, "] ");
3143                         chattingPartner = p; break;
3144                     }
3145                 } else
3146                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3147                 for(p=0; p<MAX_CHAT; p++) {
3148                     if(!strcmp("whispers", chatPartner[p])) {
3149                         talker[0] = '['; strcat(talker, "] ");
3150                         chattingPartner = p; break;
3151                     }
3152                 } else
3153                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3154                   if(buf[i-8] == '-' && buf[i-3] == 't')
3155                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3156                     if(!strcmp("c-shouts", chatPartner[p])) {
3157                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3158                         chattingPartner = p; break;
3159                     }
3160                   }
3161                   if(chattingPartner < 0)
3162                   for(p=0; p<MAX_CHAT; p++) {
3163                     if(!strcmp("shouts", chatPartner[p])) {
3164                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3165                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3166                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3167                         chattingPartner = p; break;
3168                     }
3169                   }
3170                 }
3171                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3172                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3173                     talker[0] = 0; Colorize(ColorTell, FALSE);
3174                     chattingPartner = p; break;
3175                 }
3176                 if(chattingPartner<0) i = oldi; else {
3177                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3178                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3179                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3180                     started = STARTED_COMMENT;
3181                     parse_pos = 0; parse[0] = NULLCHAR;
3182                     savingComment = 3 + chattingPartner; // counts as TRUE
3183                     suppressKibitz = TRUE;
3184                     continue;
3185                 }
3186             } // [HGM] chat: end of patch
3187
3188           backup = i;
3189             if (appData.zippyTalk || appData.zippyPlay) {
3190                 /* [DM] Backup address for color zippy lines */
3191 #if ZIPPY
3192                if (loggedOn == TRUE)
3193                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3194                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3195 #endif
3196             } // [DM] 'else { ' deleted
3197                 if (
3198                     /* Regular tells and says */
3199                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3200                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3201                     looking_at(buf, &i, "* says: ") ||
3202                     /* Don't color "message" or "messages" output */
3203                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3204                     looking_at(buf, &i, "*. * at *:*: ") ||
3205                     looking_at(buf, &i, "--* (*:*): ") ||
3206                     /* Message notifications (same color as tells) */
3207                     looking_at(buf, &i, "* has left a message ") ||
3208                     looking_at(buf, &i, "* just sent you a message:\n") ||
3209                     /* Whispers and kibitzes */
3210                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3211                     looking_at(buf, &i, "* kibitzes: ") ||
3212                     /* Channel tells */
3213                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3214
3215                   if (tkind == 1 && strchr(star_match[0], ':')) {
3216                       /* Avoid "tells you:" spoofs in channels */
3217                      tkind = 3;
3218                   }
3219                   if (star_match[0][0] == NULLCHAR ||
3220                       strchr(star_match[0], ' ') ||
3221                       (tkind == 3 && strchr(star_match[1], ' '))) {
3222                     /* Reject bogus matches */
3223                     i = oldi;
3224                   } else {
3225                     if (appData.colorize) {
3226                       if (oldi > next_out) {
3227                         SendToPlayer(&buf[next_out], oldi - next_out);
3228                         next_out = oldi;
3229                       }
3230                       switch (tkind) {
3231                       case 1:
3232                         Colorize(ColorTell, FALSE);
3233                         curColor = ColorTell;
3234                         break;
3235                       case 2:
3236                         Colorize(ColorKibitz, FALSE);
3237                         curColor = ColorKibitz;
3238                         break;
3239                       case 3:
3240                         p = strrchr(star_match[1], '(');
3241                         if (p == NULL) {
3242                           p = star_match[1];
3243                         } else {
3244                           p++;
3245                         }
3246                         if (atoi(p) == 1) {
3247                           Colorize(ColorChannel1, FALSE);
3248                           curColor = ColorChannel1;
3249                         } else {
3250                           Colorize(ColorChannel, FALSE);
3251                           curColor = ColorChannel;
3252                         }
3253                         break;
3254                       case 5:
3255                         curColor = ColorNormal;
3256                         break;
3257                       }
3258                     }
3259                     if (started == STARTED_NONE && appData.autoComment &&
3260                         (gameMode == IcsObserving ||
3261                          gameMode == IcsPlayingWhite ||
3262                          gameMode == IcsPlayingBlack)) {
3263                       parse_pos = i - oldi;
3264                       memcpy(parse, &buf[oldi], parse_pos);
3265                       parse[parse_pos] = NULLCHAR;
3266                       started = STARTED_COMMENT;
3267                       savingComment = TRUE;
3268                     } else {
3269                       started = STARTED_CHATTER;
3270                       savingComment = FALSE;
3271                     }
3272                     loggedOn = TRUE;
3273                     continue;
3274                   }
3275                 }
3276
3277                 if (looking_at(buf, &i, "* s-shouts: ") ||
3278                     looking_at(buf, &i, "* c-shouts: ")) {
3279                     if (appData.colorize) {
3280                         if (oldi > next_out) {
3281                             SendToPlayer(&buf[next_out], oldi - next_out);
3282                             next_out = oldi;
3283                         }
3284                         Colorize(ColorSShout, FALSE);
3285                         curColor = ColorSShout;
3286                     }
3287                     loggedOn = TRUE;
3288                     started = STARTED_CHATTER;
3289                     continue;
3290                 }
3291
3292                 if (looking_at(buf, &i, "--->")) {
3293                     loggedOn = TRUE;
3294                     continue;
3295                 }
3296
3297                 if (looking_at(buf, &i, "* shouts: ") ||
3298                     looking_at(buf, &i, "--> ")) {
3299                     if (appData.colorize) {
3300                         if (oldi > next_out) {
3301                             SendToPlayer(&buf[next_out], oldi - next_out);
3302                             next_out = oldi;
3303                         }
3304                         Colorize(ColorShout, FALSE);
3305                         curColor = ColorShout;
3306                     }
3307                     loggedOn = TRUE;
3308                     started = STARTED_CHATTER;
3309                     continue;
3310                 }
3311
3312                 if (looking_at( buf, &i, "Challenge:")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorChallenge, FALSE);
3319                         curColor = ColorChallenge;
3320                     }
3321                     loggedOn = TRUE;
3322                     continue;
3323                 }
3324
3325                 if (looking_at(buf, &i, "* offers you") ||
3326                     looking_at(buf, &i, "* offers to be") ||
3327                     looking_at(buf, &i, "* would like to") ||
3328                     looking_at(buf, &i, "* requests to") ||
3329                     looking_at(buf, &i, "Your opponent offers") ||
3330                     looking_at(buf, &i, "Your opponent requests")) {
3331
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorRequest, FALSE);
3338                         curColor = ColorRequest;
3339                     }
3340                     continue;
3341                 }
3342
3343                 if (looking_at(buf, &i, "* (*) seeking")) {
3344                     if (appData.colorize) {
3345                         if (oldi > next_out) {
3346                             SendToPlayer(&buf[next_out], oldi - next_out);
3347                             next_out = oldi;
3348                         }
3349                         Colorize(ColorSeek, FALSE);
3350                         curColor = ColorSeek;
3351                     }
3352                     continue;
3353             }
3354
3355           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3356
3357             if (looking_at(buf, &i, "\\   ")) {
3358                 if (prevColor != ColorNormal) {
3359                     if (oldi > next_out) {
3360                         SendToPlayer(&buf[next_out], oldi - next_out);
3361                         next_out = oldi;
3362                     }
3363                     Colorize(prevColor, TRUE);
3364                     curColor = prevColor;
3365                 }
3366                 if (savingComment) {
3367                     parse_pos = i - oldi;
3368                     memcpy(parse, &buf[oldi], parse_pos);
3369                     parse[parse_pos] = NULLCHAR;
3370                     started = STARTED_COMMENT;
3371                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3372                         chattingPartner = savingComment - 3; // kludge to remember the box
3373                 } else {
3374                     started = STARTED_CHATTER;
3375                 }
3376                 continue;
3377             }
3378
3379             if (looking_at(buf, &i, "Black Strength :") ||
3380                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3381                 looking_at(buf, &i, "<10>") ||
3382                 looking_at(buf, &i, "#@#")) {
3383                 /* Wrong board style */
3384                 loggedOn = TRUE;
3385                 SendToICS(ics_prefix);
3386                 SendToICS("set style 12\n");
3387                 SendToICS(ics_prefix);
3388                 SendToICS("refresh\n");
3389                 continue;
3390             }
3391
3392             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3393                 ICSInitScript();
3394                 have_sent_ICS_logon = 1;
3395                 continue;
3396             }
3397
3398             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3399                 (looking_at(buf, &i, "\n<12> ") ||
3400                  looking_at(buf, &i, "<12> "))) {
3401                 loggedOn = TRUE;
3402                 if (oldi > next_out) {
3403                     SendToPlayer(&buf[next_out], oldi - next_out);
3404                 }
3405                 next_out = i;
3406                 started = STARTED_BOARD;
3407                 parse_pos = 0;
3408                 continue;
3409             }
3410
3411             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3412                 looking_at(buf, &i, "<b1> ")) {
3413                 if (oldi > next_out) {
3414                     SendToPlayer(&buf[next_out], oldi - next_out);
3415                 }
3416                 next_out = i;
3417                 started = STARTED_HOLDINGS;
3418                 parse_pos = 0;
3419                 continue;
3420             }
3421
3422             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3423                 loggedOn = TRUE;
3424                 /* Header for a move list -- first line */
3425
3426                 switch (ics_getting_history) {
3427                   case H_FALSE:
3428                     switch (gameMode) {
3429                       case IcsIdle:
3430                       case BeginningOfGame:
3431                         /* User typed "moves" or "oldmoves" while we
3432                            were idle.  Pretend we asked for these
3433                            moves and soak them up so user can step
3434                            through them and/or save them.
3435                            */
3436                         Reset(FALSE, TRUE);
3437                         gameMode = IcsObserving;
3438                         ModeHighlight();
3439                         ics_gamenum = -1;
3440                         ics_getting_history = H_GOT_UNREQ_HEADER;
3441                         break;
3442                       case EditGame: /*?*/
3443                       case EditPosition: /*?*/
3444                         /* Should above feature work in these modes too? */
3445                         /* For now it doesn't */
3446                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3447                         break;
3448                       default:
3449                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3450                         break;
3451                     }
3452                     break;
3453                   case H_REQUESTED:
3454                     /* Is this the right one? */
3455                     if (gameInfo.white && gameInfo.black &&
3456                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3457                         strcmp(gameInfo.black, star_match[2]) == 0) {
3458                         /* All is well */
3459                         ics_getting_history = H_GOT_REQ_HEADER;
3460                     }
3461                     break;
3462                   case H_GOT_REQ_HEADER:
3463                   case H_GOT_UNREQ_HEADER:
3464                   case H_GOT_UNWANTED_HEADER:
3465                   case H_GETTING_MOVES:
3466                     /* Should not happen */
3467                     DisplayError(_("Error gathering move list: two headers"), 0);
3468                     ics_getting_history = H_FALSE;
3469                     break;
3470                 }
3471
3472                 /* Save player ratings into gameInfo if needed */
3473                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3474                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3475                     (gameInfo.whiteRating == -1 ||
3476                      gameInfo.blackRating == -1)) {
3477
3478                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3479                     gameInfo.blackRating = string_to_rating(star_match[3]);
3480                     if (appData.debugMode)
3481                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3482                               gameInfo.whiteRating, gameInfo.blackRating);
3483                 }
3484                 continue;
3485             }
3486
3487             if (looking_at(buf, &i,
3488               "* * match, initial time: * minute*, increment: * second")) {
3489                 /* Header for a move list -- second line */
3490                 /* Initial board will follow if this is a wild game */
3491                 if (gameInfo.event != NULL) free(gameInfo.event);
3492                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3493                 gameInfo.event = StrSave(str);
3494                 /* [HGM] we switched variant. Translate boards if needed. */
3495                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3496                 continue;
3497             }
3498
3499             if (looking_at(buf, &i, "Move  ")) {
3500                 /* Beginning of a move list */
3501                 switch (ics_getting_history) {
3502                   case H_FALSE:
3503                     /* Normally should not happen */
3504                     /* Maybe user hit reset while we were parsing */
3505                     break;
3506                   case H_REQUESTED:
3507                     /* Happens if we are ignoring a move list that is not
3508                      * the one we just requested.  Common if the user
3509                      * tries to observe two games without turning off
3510                      * getMoveList */
3511                     break;
3512                   case H_GETTING_MOVES:
3513                     /* Should not happen */
3514                     DisplayError(_("Error gathering move list: nested"), 0);
3515                     ics_getting_history = H_FALSE;
3516                     break;
3517                   case H_GOT_REQ_HEADER:
3518                     ics_getting_history = H_GETTING_MOVES;
3519                     started = STARTED_MOVES;
3520                     parse_pos = 0;
3521                     if (oldi > next_out) {
3522                         SendToPlayer(&buf[next_out], oldi - next_out);
3523                     }
3524                     break;
3525                   case H_GOT_UNREQ_HEADER:
3526                     ics_getting_history = H_GETTING_MOVES;
3527                     started = STARTED_MOVES_NOHIDE;
3528                     parse_pos = 0;
3529                     break;
3530                   case H_GOT_UNWANTED_HEADER:
3531                     ics_getting_history = H_FALSE;
3532                     break;
3533                 }
3534                 continue;
3535             }
3536
3537             if (looking_at(buf, &i, "% ") ||
3538                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3539                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3540                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3541                     soughtPending = FALSE;
3542                     seekGraphUp = TRUE;
3543                     DrawSeekGraph();
3544                 }
3545                 if(suppressKibitz) next_out = i;
3546                 savingComment = FALSE;
3547                 suppressKibitz = 0;
3548                 switch (started) {
3549                   case STARTED_MOVES:
3550                   case STARTED_MOVES_NOHIDE:
3551                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3552                     parse[parse_pos + i - oldi] = NULLCHAR;
3553                     ParseGameHistory(parse);
3554 #if ZIPPY
3555                     if (appData.zippyPlay && first.initDone) {
3556                         FeedMovesToProgram(&first, forwardMostMove);
3557                         if (gameMode == IcsPlayingWhite) {
3558                             if (WhiteOnMove(forwardMostMove)) {
3559                                 if (first.sendTime) {
3560                                   if (first.useColors) {
3561                                     SendToProgram("black\n", &first);
3562                                   }
3563                                   SendTimeRemaining(&first, TRUE);
3564                                 }
3565                                 if (first.useColors) {
3566                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3567                                 }
3568                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3569                                 first.maybeThinking = TRUE;
3570                             } else {
3571                                 if (first.usePlayother) {
3572                                   if (first.sendTime) {
3573                                     SendTimeRemaining(&first, TRUE);
3574                                   }
3575                                   SendToProgram("playother\n", &first);
3576                                   firstMove = FALSE;
3577                                 } else {
3578                                   firstMove = TRUE;
3579                                 }
3580                             }
3581                         } else if (gameMode == IcsPlayingBlack) {
3582                             if (!WhiteOnMove(forwardMostMove)) {
3583                                 if (first.sendTime) {
3584                                   if (first.useColors) {
3585                                     SendToProgram("white\n", &first);
3586                                   }
3587                                   SendTimeRemaining(&first, FALSE);
3588                                 }
3589                                 if (first.useColors) {
3590                                   SendToProgram("black\n", &first);
3591                                 }
3592                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3593                                 first.maybeThinking = TRUE;
3594                             } else {
3595                                 if (first.usePlayother) {
3596                                   if (first.sendTime) {
3597                                     SendTimeRemaining(&first, FALSE);
3598                                   }
3599                                   SendToProgram("playother\n", &first);
3600                                   firstMove = FALSE;
3601                                 } else {
3602                                   firstMove = TRUE;
3603                                 }
3604                             }
3605                         }
3606                     }
3607 #endif
3608                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3609                         /* Moves came from oldmoves or moves command
3610                            while we weren't doing anything else.
3611                            */
3612                         currentMove = forwardMostMove;
3613                         ClearHighlights();/*!!could figure this out*/
3614                         flipView = appData.flipView;
3615                         DrawPosition(TRUE, boards[currentMove]);
3616                         DisplayBothClocks();
3617                         snprintf(str, MSG_SIZ, "%s vs. %s",
3618                                 gameInfo.white, gameInfo.black);
3619                         DisplayTitle(str);
3620                         gameMode = IcsIdle;
3621                     } else {
3622                         /* Moves were history of an active game */
3623                         if (gameInfo.resultDetails != NULL) {
3624                             free(gameInfo.resultDetails);
3625                             gameInfo.resultDetails = NULL;
3626                         }
3627                     }
3628                     HistorySet(parseList, backwardMostMove,
3629                                forwardMostMove, currentMove-1);
3630                     DisplayMove(currentMove - 1);
3631                     if (started == STARTED_MOVES) next_out = i;
3632                     started = STARTED_NONE;
3633                     ics_getting_history = H_FALSE;
3634                     break;
3635
3636                   case STARTED_OBSERVE:
3637                     started = STARTED_NONE;
3638                     SendToICS(ics_prefix);
3639                     SendToICS("refresh\n");
3640                     break;
3641
3642                   default:
3643                     break;
3644                 }
3645                 if(bookHit) { // [HGM] book: simulate book reply
3646                     static char bookMove[MSG_SIZ]; // a bit generous?
3647
3648                     programStats.nodes = programStats.depth = programStats.time =
3649                     programStats.score = programStats.got_only_move = 0;
3650                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3651
3652                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3653                     strcat(bookMove, bookHit);
3654                     HandleMachineMove(bookMove, &first);
3655                 }
3656                 continue;
3657             }
3658
3659             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3660                  started == STARTED_HOLDINGS ||
3661                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3662                 /* Accumulate characters in move list or board */
3663                 parse[parse_pos++] = buf[i];
3664             }
3665
3666             /* Start of game messages.  Mostly we detect start of game
3667                when the first board image arrives.  On some versions
3668                of the ICS, though, we need to do a "refresh" after starting
3669                to observe in order to get the current board right away. */
3670             if (looking_at(buf, &i, "Adding game * to observation list")) {
3671                 started = STARTED_OBSERVE;
3672                 continue;
3673             }
3674
3675             /* Handle auto-observe */
3676             if (appData.autoObserve &&
3677                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3678                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3679                 char *player;
3680                 /* Choose the player that was highlighted, if any. */
3681                 if (star_match[0][0] == '\033' ||
3682                     star_match[1][0] != '\033') {
3683                     player = star_match[0];
3684                 } else {
3685                     player = star_match[2];
3686                 }
3687                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3688                         ics_prefix, StripHighlightAndTitle(player));
3689                 SendToICS(str);
3690
3691                 /* Save ratings from notify string */
3692                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3693                 player1Rating = string_to_rating(star_match[1]);
3694                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3695                 player2Rating = string_to_rating(star_match[3]);
3696
3697                 if (appData.debugMode)
3698                   fprintf(debugFP,
3699                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3700                           player1Name, player1Rating,
3701                           player2Name, player2Rating);
3702
3703                 continue;
3704             }
3705
3706             /* Deal with automatic examine mode after a game,
3707                and with IcsObserving -> IcsExamining transition */
3708             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3709                 looking_at(buf, &i, "has made you an examiner of game *")) {
3710
3711                 int gamenum = atoi(star_match[0]);
3712                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3713                     gamenum == ics_gamenum) {
3714                     /* We were already playing or observing this game;
3715                        no need to refetch history */
3716                     gameMode = IcsExamining;
3717                     if (pausing) {
3718                         pauseExamForwardMostMove = forwardMostMove;
3719                     } else if (currentMove < forwardMostMove) {
3720                         ForwardInner(forwardMostMove);
3721                     }
3722                 } else {
3723                     /* I don't think this case really can happen */
3724                     SendToICS(ics_prefix);
3725                     SendToICS("refresh\n");
3726                 }
3727                 continue;
3728             }
3729
3730             /* Error messages */
3731 //          if (ics_user_moved) {
3732             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3733                 if (looking_at(buf, &i, "Illegal move") ||
3734                     looking_at(buf, &i, "Not a legal move") ||
3735                     looking_at(buf, &i, "Your king is in check") ||
3736                     looking_at(buf, &i, "It isn't your turn") ||
3737                     looking_at(buf, &i, "It is not your move")) {
3738                     /* Illegal move */
3739                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3740                         currentMove = forwardMostMove-1;
3741                         DisplayMove(currentMove - 1); /* before DMError */
3742                         DrawPosition(FALSE, boards[currentMove]);
3743                         SwitchClocks(forwardMostMove-1); // [HGM] race
3744                         DisplayBothClocks();
3745                     }
3746                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3747                     ics_user_moved = 0;
3748                     continue;
3749                 }
3750             }
3751
3752             if (looking_at(buf, &i, "still have time") ||
3753                 looking_at(buf, &i, "not out of time") ||
3754                 looking_at(buf, &i, "either player is out of time") ||
3755                 looking_at(buf, &i, "has timeseal; checking")) {
3756                 /* We must have called his flag a little too soon */
3757                 whiteFlag = blackFlag = FALSE;
3758                 continue;
3759             }
3760
3761             if (looking_at(buf, &i, "added * seconds to") ||
3762                 looking_at(buf, &i, "seconds were added to")) {
3763                 /* Update the clocks */
3764                 SendToICS(ics_prefix);
3765                 SendToICS("refresh\n");
3766                 continue;
3767             }
3768
3769             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3770                 ics_clock_paused = TRUE;
3771                 StopClocks();
3772                 continue;
3773             }
3774
3775             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3776                 ics_clock_paused = FALSE;
3777                 StartClocks();
3778                 continue;
3779             }
3780
3781             /* Grab player ratings from the Creating: message.
3782                Note we have to check for the special case when
3783                the ICS inserts things like [white] or [black]. */
3784             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3785                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3786                 /* star_matches:
3787                    0    player 1 name (not necessarily white)
3788                    1    player 1 rating
3789                    2    empty, white, or black (IGNORED)
3790                    3    player 2 name (not necessarily black)
3791                    4    player 2 rating
3792
3793                    The names/ratings are sorted out when the game
3794                    actually starts (below).
3795                 */
3796                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3797                 player1Rating = string_to_rating(star_match[1]);
3798                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3799                 player2Rating = string_to_rating(star_match[4]);
3800
3801                 if (appData.debugMode)
3802                   fprintf(debugFP,
3803                           "Ratings from 'Creating:' %s %d, %s %d\n",
3804                           player1Name, player1Rating,
3805                           player2Name, player2Rating);
3806
3807                 continue;
3808             }
3809
3810             /* Improved generic start/end-of-game messages */
3811             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3812                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3813                 /* If tkind == 0: */
3814                 /* star_match[0] is the game number */
3815                 /*           [1] is the white player's name */
3816                 /*           [2] is the black player's name */
3817                 /* For end-of-game: */
3818                 /*           [3] is the reason for the game end */
3819                 /*           [4] is a PGN end game-token, preceded by " " */
3820                 /* For start-of-game: */
3821                 /*           [3] begins with "Creating" or "Continuing" */
3822                 /*           [4] is " *" or empty (don't care). */
3823                 int gamenum = atoi(star_match[0]);
3824                 char *whitename, *blackname, *why, *endtoken;
3825                 ChessMove endtype = EndOfFile;
3826
3827                 if (tkind == 0) {
3828                   whitename = star_match[1];
3829                   blackname = star_match[2];
3830                   why = star_match[3];
3831                   endtoken = star_match[4];
3832                 } else {
3833                   whitename = star_match[1];
3834                   blackname = star_match[3];
3835                   why = star_match[5];
3836                   endtoken = star_match[6];
3837                 }
3838
3839                 /* Game start messages */
3840                 if (strncmp(why, "Creating ", 9) == 0 ||
3841                     strncmp(why, "Continuing ", 11) == 0) {
3842                     gs_gamenum = gamenum;
3843                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3844                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3845 #if ZIPPY
3846                     if (appData.zippyPlay) {
3847                         ZippyGameStart(whitename, blackname);
3848                     }
3849 #endif /*ZIPPY*/
3850                     partnerBoardValid = FALSE; // [HGM] bughouse
3851                     continue;
3852                 }
3853
3854                 /* Game end messages */
3855                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3856                     ics_gamenum != gamenum) {
3857                     continue;
3858                 }
3859                 while (endtoken[0] == ' ') endtoken++;
3860                 switch (endtoken[0]) {
3861                   case '*':
3862                   default:
3863                     endtype = GameUnfinished;
3864                     break;
3865                   case '0':
3866                     endtype = BlackWins;
3867                     break;
3868                   case '1':
3869                     if (endtoken[1] == '/')
3870                       endtype = GameIsDrawn;
3871                     else
3872                       endtype = WhiteWins;
3873                     break;
3874                 }
3875                 GameEnds(endtype, why, GE_ICS);
3876 #if ZIPPY
3877                 if (appData.zippyPlay && first.initDone) {
3878                     ZippyGameEnd(endtype, why);
3879                     if (first.pr == NULL) {
3880                       /* Start the next process early so that we'll
3881                          be ready for the next challenge */
3882                       StartChessProgram(&first);
3883                     }
3884                     /* Send "new" early, in case this command takes
3885                        a long time to finish, so that we'll be ready
3886                        for the next challenge. */
3887                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3888                     Reset(TRUE, TRUE);
3889                 }
3890 #endif /*ZIPPY*/
3891                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3892                 continue;
3893             }
3894
3895             if (looking_at(buf, &i, "Removing game * from observation") ||
3896                 looking_at(buf, &i, "no longer observing game *") ||
3897                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3898                 if (gameMode == IcsObserving &&
3899                     atoi(star_match[0]) == ics_gamenum)
3900                   {
3901                       /* icsEngineAnalyze */
3902                       if (appData.icsEngineAnalyze) {
3903                             ExitAnalyzeMode();
3904                             ModeHighlight();
3905                       }
3906                       StopClocks();
3907                       gameMode = IcsIdle;
3908                       ics_gamenum = -1;
3909                       ics_user_moved = FALSE;
3910                   }
3911                 continue;
3912             }
3913
3914             if (looking_at(buf, &i, "no longer examining game *")) {
3915                 if (gameMode == IcsExamining &&
3916                     atoi(star_match[0]) == ics_gamenum)
3917                   {
3918                       gameMode = IcsIdle;
3919                       ics_gamenum = -1;
3920                       ics_user_moved = FALSE;
3921                   }
3922                 continue;
3923             }
3924
3925             /* Advance leftover_start past any newlines we find,
3926                so only partial lines can get reparsed */
3927             if (looking_at(buf, &i, "\n")) {
3928                 prevColor = curColor;
3929                 if (curColor != ColorNormal) {
3930                     if (oldi > next_out) {
3931                         SendToPlayer(&buf[next_out], oldi - next_out);
3932                         next_out = oldi;
3933                     }
3934                     Colorize(ColorNormal, FALSE);
3935                     curColor = ColorNormal;
3936                 }
3937                 if (started == STARTED_BOARD) {
3938                     started = STARTED_NONE;
3939                     parse[parse_pos] = NULLCHAR;
3940                     ParseBoard12(parse);
3941                     ics_user_moved = 0;
3942
3943                     /* Send premove here */
3944                     if (appData.premove) {
3945                       char str[MSG_SIZ];
3946                       if (currentMove == 0 &&
3947                           gameMode == IcsPlayingWhite &&
3948                           appData.premoveWhite) {
3949                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3950                         if (appData.debugMode)
3951                           fprintf(debugFP, "Sending premove:\n");
3952                         SendToICS(str);
3953                       } else if (currentMove == 1 &&
3954                                  gameMode == IcsPlayingBlack &&
3955                                  appData.premoveBlack) {
3956                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3957                         if (appData.debugMode)
3958                           fprintf(debugFP, "Sending premove:\n");
3959                         SendToICS(str);
3960                       } else if (gotPremove) {
3961                         gotPremove = 0;
3962                         ClearPremoveHighlights();
3963                         if (appData.debugMode)
3964                           fprintf(debugFP, "Sending premove:\n");
3965                           UserMoveEvent(premoveFromX, premoveFromY,
3966                                         premoveToX, premoveToY,
3967                                         premovePromoChar);
3968                       }
3969                     }
3970
3971                     /* Usually suppress following prompt */
3972                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3973                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3974                         if (looking_at(buf, &i, "*% ")) {
3975                             savingComment = FALSE;
3976                             suppressKibitz = 0;
3977                         }
3978                     }
3979                     next_out = i;
3980                 } else if (started == STARTED_HOLDINGS) {
3981                     int gamenum;
3982                     char new_piece[MSG_SIZ];
3983                     started = STARTED_NONE;
3984                     parse[parse_pos] = NULLCHAR;
3985                     if (appData.debugMode)
3986                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3987                                                         parse, currentMove);
3988                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3989                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3990                         if (gameInfo.variant == VariantNormal) {
3991                           /* [HGM] We seem to switch variant during a game!
3992                            * Presumably no holdings were displayed, so we have
3993                            * to move the position two files to the right to
3994                            * create room for them!
3995                            */
3996                           VariantClass newVariant;
3997                           switch(gameInfo.boardWidth) { // base guess on board width
3998                                 case 9:  newVariant = VariantShogi; break;
3999                                 case 10: newVariant = VariantGreat; break;
4000                                 default: newVariant = VariantCrazyhouse; break;
4001                           }
4002                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4003                           /* Get a move list just to see the header, which
4004                              will tell us whether this is really bug or zh */
4005                           if (ics_getting_history == H_FALSE) {
4006                             ics_getting_history = H_REQUESTED;
4007                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4008                             SendToICS(str);
4009                           }
4010                         }
4011                         new_piece[0] = NULLCHAR;
4012                         sscanf(parse, "game %d white [%s black [%s <- %s",
4013                                &gamenum, white_holding, black_holding,
4014                                new_piece);
4015                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4016                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4017                         /* [HGM] copy holdings to board holdings area */
4018                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4019                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4020                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4021 #if ZIPPY
4022                         if (appData.zippyPlay && first.initDone) {
4023                             ZippyHoldings(white_holding, black_holding,
4024                                           new_piece);
4025                         }
4026 #endif /*ZIPPY*/
4027                         if (tinyLayout || smallLayout) {
4028                             char wh[16], bh[16];
4029                             PackHolding(wh, white_holding);
4030                             PackHolding(bh, black_holding);
4031                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4032                                     gameInfo.white, gameInfo.black);
4033                         } else {
4034                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4035                                     gameInfo.white, white_holding,
4036                                     gameInfo.black, black_holding);
4037                         }
4038                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4039                         DrawPosition(FALSE, boards[currentMove]);
4040                         DisplayTitle(str);
4041                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4042                         sscanf(parse, "game %d white [%s black [%s <- %s",
4043                                &gamenum, white_holding, black_holding,
4044                                new_piece);
4045                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4046                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4047                         /* [HGM] copy holdings to partner-board holdings area */
4048                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4049                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4050                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4051                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4052                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4053                       }
4054                     }
4055                     /* Suppress following prompt */
4056                     if (looking_at(buf, &i, "*% ")) {
4057                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4058                         savingComment = FALSE;
4059                         suppressKibitz = 0;
4060                     }
4061                     next_out = i;
4062                 }
4063                 continue;
4064             }
4065
4066             i++;                /* skip unparsed character and loop back */
4067         }
4068
4069         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4070 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4071 //          SendToPlayer(&buf[next_out], i - next_out);
4072             started != STARTED_HOLDINGS && leftover_start > next_out) {
4073             SendToPlayer(&buf[next_out], leftover_start - next_out);
4074             next_out = i;
4075         }
4076
4077         leftover_len = buf_len - leftover_start;
4078         /* if buffer ends with something we couldn't parse,
4079            reparse it after appending the next read */
4080
4081     } else if (count == 0) {
4082         RemoveInputSource(isr);
4083         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4084     } else {
4085         DisplayFatalError(_("Error reading from ICS"), error, 1);
4086     }
4087 }
4088
4089
4090 /* Board style 12 looks like this:
4091
4092    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4093
4094  * The "<12> " is stripped before it gets to this routine.  The two
4095  * trailing 0's (flip state and clock ticking) are later addition, and
4096  * some chess servers may not have them, or may have only the first.
4097  * Additional trailing fields may be added in the future.
4098  */
4099
4100 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4101
4102 #define RELATION_OBSERVING_PLAYED    0
4103 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4104 #define RELATION_PLAYING_MYMOVE      1
4105 #define RELATION_PLAYING_NOTMYMOVE  -1
4106 #define RELATION_EXAMINING           2
4107 #define RELATION_ISOLATED_BOARD     -3
4108 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4109
4110 void
4111 ParseBoard12(string)
4112      char *string;
4113 {
4114     GameMode newGameMode;
4115     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4116     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4117     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4118     char to_play, board_chars[200];
4119     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4120     char black[32], white[32];
4121     Board board;
4122     int prevMove = currentMove;
4123     int ticking = 2;
4124     ChessMove moveType;
4125     int fromX, fromY, toX, toY;
4126     char promoChar;
4127     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4128     char *bookHit = NULL; // [HGM] book
4129     Boolean weird = FALSE, reqFlag = FALSE;
4130
4131     fromX = fromY = toX = toY = -1;
4132
4133     newGame = FALSE;
4134
4135     if (appData.debugMode)
4136       fprintf(debugFP, _("Parsing board: %s\n"), string);
4137
4138     move_str[0] = NULLCHAR;
4139     elapsed_time[0] = NULLCHAR;
4140     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4141         int  i = 0, j;
4142         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4143             if(string[i] == ' ') { ranks++; files = 0; }
4144             else files++;
4145             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4146             i++;
4147         }
4148         for(j = 0; j <i; j++) board_chars[j] = string[j];
4149         board_chars[i] = '\0';
4150         string += i + 1;
4151     }
4152     n = sscanf(string, PATTERN, &to_play, &double_push,
4153                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4154                &gamenum, white, black, &relation, &basetime, &increment,
4155                &white_stren, &black_stren, &white_time, &black_time,
4156                &moveNum, str, elapsed_time, move_str, &ics_flip,
4157                &ticking);
4158
4159     if (n < 21) {
4160         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4161         DisplayError(str, 0);
4162         return;
4163     }
4164
4165     /* Convert the move number to internal form */
4166     moveNum = (moveNum - 1) * 2;
4167     if (to_play == 'B') moveNum++;
4168     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4169       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4170                         0, 1);
4171       return;
4172     }
4173
4174     switch (relation) {
4175       case RELATION_OBSERVING_PLAYED:
4176       case RELATION_OBSERVING_STATIC:
4177         if (gamenum == -1) {
4178             /* Old ICC buglet */
4179             relation = RELATION_OBSERVING_STATIC;
4180         }
4181         newGameMode = IcsObserving;
4182         break;
4183       case RELATION_PLAYING_MYMOVE:
4184       case RELATION_PLAYING_NOTMYMOVE:
4185         newGameMode =
4186           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4187             IcsPlayingWhite : IcsPlayingBlack;
4188         break;
4189       case RELATION_EXAMINING:
4190         newGameMode = IcsExamining;
4191         break;
4192       case RELATION_ISOLATED_BOARD:
4193       default:
4194         /* Just display this board.  If user was doing something else,
4195            we will forget about it until the next board comes. */
4196         newGameMode = IcsIdle;
4197         break;
4198       case RELATION_STARTING_POSITION:
4199         newGameMode = gameMode;
4200         break;
4201     }
4202
4203     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4204          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4205       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4206       char *toSqr;
4207       for (k = 0; k < ranks; k++) {
4208         for (j = 0; j < files; j++)
4209           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4210         if(gameInfo.holdingsWidth > 1) {
4211              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4212              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4213         }
4214       }
4215       CopyBoard(partnerBoard, board);
4216       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4217         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4218         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4219       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4220       if(toSqr = strchr(str, '-')) {
4221         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4222         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4223       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4224       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4225       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4226       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4227       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4228       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4229                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4230       DisplayMessage(partnerStatus, "");
4231         partnerBoardValid = TRUE;
4232       return;
4233     }
4234
4235     /* Modify behavior for initial board display on move listing
4236        of wild games.
4237        */
4238     switch (ics_getting_history) {
4239       case H_FALSE:
4240       case H_REQUESTED:
4241         break;
4242       case H_GOT_REQ_HEADER:
4243       case H_GOT_UNREQ_HEADER:
4244         /* This is the initial position of the current game */
4245         gamenum = ics_gamenum;
4246         moveNum = 0;            /* old ICS bug workaround */
4247         if (to_play == 'B') {
4248           startedFromSetupPosition = TRUE;
4249           blackPlaysFirst = TRUE;
4250           moveNum = 1;
4251           if (forwardMostMove == 0) forwardMostMove = 1;
4252           if (backwardMostMove == 0) backwardMostMove = 1;
4253           if (currentMove == 0) currentMove = 1;
4254         }
4255         newGameMode = gameMode;
4256         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4257         break;
4258       case H_GOT_UNWANTED_HEADER:
4259         /* This is an initial board that we don't want */
4260         return;
4261       case H_GETTING_MOVES:
4262         /* Should not happen */
4263         DisplayError(_("Error gathering move list: extra board"), 0);
4264         ics_getting_history = H_FALSE;
4265         return;
4266     }
4267
4268    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4269                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4270      /* [HGM] We seem to have switched variant unexpectedly
4271       * Try to guess new variant from board size
4272       */
4273           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4274           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4275           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4276           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4277           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4278           if(!weird) newVariant = VariantNormal;
4279           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4280           /* Get a move list just to see the header, which
4281              will tell us whether this is really bug or zh */
4282           if (ics_getting_history == H_FALSE) {
4283             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4284             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4285             SendToICS(str);
4286           }
4287     }
4288
4289     /* Take action if this is the first board of a new game, or of a
4290        different game than is currently being displayed.  */
4291     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4292         relation == RELATION_ISOLATED_BOARD) {
4293
4294         /* Forget the old game and get the history (if any) of the new one */
4295         if (gameMode != BeginningOfGame) {
4296           Reset(TRUE, TRUE);
4297         }
4298         newGame = TRUE;
4299         if (appData.autoRaiseBoard) BoardToTop();
4300         prevMove = -3;
4301         if (gamenum == -1) {
4302             newGameMode = IcsIdle;
4303         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4304                    appData.getMoveList && !reqFlag) {
4305             /* Need to get game history */
4306             ics_getting_history = H_REQUESTED;
4307             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4308             SendToICS(str);
4309         }
4310
4311         /* Initially flip the board to have black on the bottom if playing
4312            black or if the ICS flip flag is set, but let the user change
4313            it with the Flip View button. */
4314         flipView = appData.autoFlipView ?
4315           (newGameMode == IcsPlayingBlack) || ics_flip :
4316           appData.flipView;
4317
4318         /* Done with values from previous mode; copy in new ones */
4319         gameMode = newGameMode;
4320         ModeHighlight();
4321         ics_gamenum = gamenum;
4322         if (gamenum == gs_gamenum) {
4323             int klen = strlen(gs_kind);
4324             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4325             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4326             gameInfo.event = StrSave(str);
4327         } else {
4328             gameInfo.event = StrSave("ICS game");
4329         }
4330         gameInfo.site = StrSave(appData.icsHost);
4331         gameInfo.date = PGNDate();
4332         gameInfo.round = StrSave("-");
4333         gameInfo.white = StrSave(white);
4334         gameInfo.black = StrSave(black);
4335         timeControl = basetime * 60 * 1000;
4336         timeControl_2 = 0;
4337         timeIncrement = increment * 1000;
4338         movesPerSession = 0;
4339         gameInfo.timeControl = TimeControlTagValue();
4340         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4341   if (appData.debugMode) {
4342     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4343     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4344     setbuf(debugFP, NULL);
4345   }
4346
4347         gameInfo.outOfBook = NULL;
4348
4349         /* Do we have the ratings? */
4350         if (strcmp(player1Name, white) == 0 &&
4351             strcmp(player2Name, black) == 0) {
4352             if (appData.debugMode)
4353               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4354                       player1Rating, player2Rating);
4355             gameInfo.whiteRating = player1Rating;
4356             gameInfo.blackRating = player2Rating;
4357         } else if (strcmp(player2Name, white) == 0 &&
4358                    strcmp(player1Name, black) == 0) {
4359             if (appData.debugMode)
4360               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4361                       player2Rating, player1Rating);
4362             gameInfo.whiteRating = player2Rating;
4363             gameInfo.blackRating = player1Rating;
4364         }
4365         player1Name[0] = player2Name[0] = NULLCHAR;
4366
4367         /* Silence shouts if requested */
4368         if (appData.quietPlay &&
4369             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4370             SendToICS(ics_prefix);
4371             SendToICS("set shout 0\n");
4372         }
4373     }
4374
4375     /* Deal with midgame name changes */
4376     if (!newGame) {
4377         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4378             if (gameInfo.white) free(gameInfo.white);
4379             gameInfo.white = StrSave(white);
4380         }
4381         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4382             if (gameInfo.black) free(gameInfo.black);
4383             gameInfo.black = StrSave(black);
4384         }
4385     }
4386
4387     /* Throw away game result if anything actually changes in examine mode */
4388     if (gameMode == IcsExamining && !newGame) {
4389         gameInfo.result = GameUnfinished;
4390         if (gameInfo.resultDetails != NULL) {
4391             free(gameInfo.resultDetails);
4392             gameInfo.resultDetails = NULL;
4393         }
4394     }
4395
4396     /* In pausing && IcsExamining mode, we ignore boards coming
4397        in if they are in a different variation than we are. */
4398     if (pauseExamInvalid) return;
4399     if (pausing && gameMode == IcsExamining) {
4400         if (moveNum <= pauseExamForwardMostMove) {
4401             pauseExamInvalid = TRUE;
4402             forwardMostMove = pauseExamForwardMostMove;
4403             return;
4404         }
4405     }
4406
4407   if (appData.debugMode) {
4408     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4409   }
4410     /* Parse the board */
4411     for (k = 0; k < ranks; k++) {
4412       for (j = 0; j < files; j++)
4413         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4414       if(gameInfo.holdingsWidth > 1) {
4415            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4416            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4417       }
4418     }
4419     CopyBoard(boards[moveNum], board);
4420     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4421     if (moveNum == 0) {
4422         startedFromSetupPosition =
4423           !CompareBoards(board, initialPosition);
4424         if(startedFromSetupPosition)
4425             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4426     }
4427
4428     /* [HGM] Set castling rights. Take the outermost Rooks,
4429        to make it also work for FRC opening positions. Note that board12
4430        is really defective for later FRC positions, as it has no way to
4431        indicate which Rook can castle if they are on the same side of King.
4432        For the initial position we grant rights to the outermost Rooks,
4433        and remember thos rights, and we then copy them on positions
4434        later in an FRC game. This means WB might not recognize castlings with
4435        Rooks that have moved back to their original position as illegal,
4436        but in ICS mode that is not its job anyway.
4437     */
4438     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4439     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4440
4441         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4442             if(board[0][i] == WhiteRook) j = i;
4443         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4444         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4445             if(board[0][i] == WhiteRook) j = i;
4446         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4447         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4448             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4449         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4450         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4451             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4452         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4453
4454         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4455         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4456             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4457         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4458             if(board[BOARD_HEIGHT-1][k] == bKing)
4459                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4460         if(gameInfo.variant == VariantTwoKings) {
4461             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4462             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4463             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4464         }
4465     } else { int r;
4466         r = boards[moveNum][CASTLING][0] = initialRights[0];
4467         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4468         r = boards[moveNum][CASTLING][1] = initialRights[1];
4469         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4470         r = boards[moveNum][CASTLING][3] = initialRights[3];
4471         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4472         r = boards[moveNum][CASTLING][4] = initialRights[4];
4473         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4474         /* wildcastle kludge: always assume King has rights */
4475         r = boards[moveNum][CASTLING][2] = initialRights[2];
4476         r = boards[moveNum][CASTLING][5] = initialRights[5];
4477     }
4478     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4479     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4480
4481
4482     if (ics_getting_history == H_GOT_REQ_HEADER ||
4483         ics_getting_history == H_GOT_UNREQ_HEADER) {
4484         /* This was an initial position from a move list, not
4485            the current position */
4486         return;
4487     }
4488
4489     /* Update currentMove and known move number limits */
4490     newMove = newGame || moveNum > forwardMostMove;
4491
4492     if (newGame) {
4493         forwardMostMove = backwardMostMove = currentMove = moveNum;
4494         if (gameMode == IcsExamining && moveNum == 0) {
4495           /* Workaround for ICS limitation: we are not told the wild
4496              type when starting to examine a game.  But if we ask for
4497              the move list, the move list header will tell us */
4498             ics_getting_history = H_REQUESTED;
4499             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4500             SendToICS(str);
4501         }
4502     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4503                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4504 #if ZIPPY
4505         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4506         /* [HGM] applied this also to an engine that is silently watching        */
4507         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4508             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4509             gameInfo.variant == currentlyInitializedVariant) {
4510           takeback = forwardMostMove - moveNum;
4511           for (i = 0; i < takeback; i++) {
4512             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4513             SendToProgram("undo\n", &first);
4514           }
4515         }
4516 #endif
4517
4518         forwardMostMove = moveNum;
4519         if (!pausing || currentMove > forwardMostMove)
4520           currentMove = forwardMostMove;
4521     } else {
4522         /* New part of history that is not contiguous with old part */
4523         if (pausing && gameMode == IcsExamining) {
4524             pauseExamInvalid = TRUE;
4525             forwardMostMove = pauseExamForwardMostMove;
4526             return;
4527         }
4528         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4529 #if ZIPPY
4530             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4531                 // [HGM] when we will receive the move list we now request, it will be
4532                 // fed to the engine from the first move on. So if the engine is not
4533                 // in the initial position now, bring it there.
4534                 InitChessProgram(&first, 0);
4535             }
4536 #endif
4537             ics_getting_history = H_REQUESTED;
4538             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4539             SendToICS(str);
4540         }
4541         forwardMostMove = backwardMostMove = currentMove = moveNum;
4542     }
4543
4544     /* Update the clocks */
4545     if (strchr(elapsed_time, '.')) {
4546       /* Time is in ms */
4547       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4548       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4549     } else {
4550       /* Time is in seconds */
4551       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4552       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4553     }
4554
4555
4556 #if ZIPPY
4557     if (appData.zippyPlay && newGame &&
4558         gameMode != IcsObserving && gameMode != IcsIdle &&
4559         gameMode != IcsExamining)
4560       ZippyFirstBoard(moveNum, basetime, increment);
4561 #endif
4562
4563     /* Put the move on the move list, first converting
4564        to canonical algebraic form. */
4565     if (moveNum > 0) {
4566   if (appData.debugMode) {
4567     if (appData.debugMode) { int f = forwardMostMove;
4568         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4569                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4570                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4571     }
4572     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4573     fprintf(debugFP, "moveNum = %d\n", moveNum);
4574     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4575     setbuf(debugFP, NULL);
4576   }
4577         if (moveNum <= backwardMostMove) {
4578             /* We don't know what the board looked like before
4579                this move.  Punt. */
4580           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4581             strcat(parseList[moveNum - 1], " ");
4582             strcat(parseList[moveNum - 1], elapsed_time);
4583             moveList[moveNum - 1][0] = NULLCHAR;
4584         } else if (strcmp(move_str, "none") == 0) {
4585             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4586             /* Again, we don't know what the board looked like;
4587                this is really the start of the game. */
4588             parseList[moveNum - 1][0] = NULLCHAR;
4589             moveList[moveNum - 1][0] = NULLCHAR;
4590             backwardMostMove = moveNum;
4591             startedFromSetupPosition = TRUE;
4592             fromX = fromY = toX = toY = -1;
4593         } else {
4594           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4595           //                 So we parse the long-algebraic move string in stead of the SAN move
4596           int valid; char buf[MSG_SIZ], *prom;
4597
4598           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4599                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4600           // str looks something like "Q/a1-a2"; kill the slash
4601           if(str[1] == '/')
4602             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4603           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4604           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4605                 strcat(buf, prom); // long move lacks promo specification!
4606           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4607                 if(appData.debugMode)
4608                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4609                 safeStrCpy(move_str, buf, MSG_SIZ);
4610           }
4611           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4612                                 &fromX, &fromY, &toX, &toY, &promoChar)
4613                || ParseOneMove(buf, moveNum - 1, &moveType,
4614                                 &fromX, &fromY, &toX, &toY, &promoChar);
4615           // end of long SAN patch
4616           if (valid) {
4617             (void) CoordsToAlgebraic(boards[moveNum - 1],
4618                                      PosFlags(moveNum - 1),
4619                                      fromY, fromX, toY, toX, promoChar,
4620                                      parseList[moveNum-1]);
4621             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4622               case MT_NONE:
4623               case MT_STALEMATE:
4624               default:
4625                 break;
4626               case MT_CHECK:
4627                 if(gameInfo.variant != VariantShogi)
4628                     strcat(parseList[moveNum - 1], "+");
4629                 break;
4630               case MT_CHECKMATE:
4631               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4632                 strcat(parseList[moveNum - 1], "#");
4633                 break;
4634             }
4635             strcat(parseList[moveNum - 1], " ");
4636             strcat(parseList[moveNum - 1], elapsed_time);
4637             /* currentMoveString is set as a side-effect of ParseOneMove */
4638             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4639             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4640             strcat(moveList[moveNum - 1], "\n");
4641
4642             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4643                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4644               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4645                 ChessSquare old, new = boards[moveNum][k][j];
4646                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4647                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4648                   if(old == new) continue;
4649                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4650                   else if(new == WhiteWazir || new == BlackWazir) {
4651                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4652                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4653                       else boards[moveNum][k][j] = old; // preserve type of Gold
4654                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4655                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4656               }
4657           } else {
4658             /* Move from ICS was illegal!?  Punt. */
4659             if (appData.debugMode) {
4660               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4661               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4662             }
4663             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4664             strcat(parseList[moveNum - 1], " ");
4665             strcat(parseList[moveNum - 1], elapsed_time);
4666             moveList[moveNum - 1][0] = NULLCHAR;
4667             fromX = fromY = toX = toY = -1;
4668           }
4669         }
4670   if (appData.debugMode) {
4671     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4672     setbuf(debugFP, NULL);
4673   }
4674
4675 #if ZIPPY
4676         /* Send move to chess program (BEFORE animating it). */
4677         if (appData.zippyPlay && !newGame && newMove &&
4678            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4679
4680             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4681                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4682                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4683                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4684                             move_str);
4685                     DisplayError(str, 0);
4686                 } else {
4687                     if (first.sendTime) {
4688                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4689                     }
4690                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4691                     if (firstMove && !bookHit) {
4692                         firstMove = FALSE;
4693                         if (first.useColors) {
4694                           SendToProgram(gameMode == IcsPlayingWhite ?
4695                                         "white\ngo\n" :
4696                                         "black\ngo\n", &first);
4697                         } else {
4698                           SendToProgram("go\n", &first);
4699                         }
4700                         first.maybeThinking = TRUE;
4701                     }
4702                 }
4703             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4704               if (moveList[moveNum - 1][0] == NULLCHAR) {
4705                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4706                 DisplayError(str, 0);
4707               } else {
4708                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4709                 SendMoveToProgram(moveNum - 1, &first);
4710               }
4711             }
4712         }
4713 #endif
4714     }
4715
4716     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4717         /* If move comes from a remote source, animate it.  If it
4718            isn't remote, it will have already been animated. */
4719         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4720             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4721         }
4722         if (!pausing && appData.highlightLastMove) {
4723             SetHighlights(fromX, fromY, toX, toY);
4724         }
4725     }
4726
4727     /* Start the clocks */
4728     whiteFlag = blackFlag = FALSE;
4729     appData.clockMode = !(basetime == 0 && increment == 0);
4730     if (ticking == 0) {
4731       ics_clock_paused = TRUE;
4732       StopClocks();
4733     } else if (ticking == 1) {
4734       ics_clock_paused = FALSE;
4735     }
4736     if (gameMode == IcsIdle ||
4737         relation == RELATION_OBSERVING_STATIC ||
4738         relation == RELATION_EXAMINING ||
4739         ics_clock_paused)
4740       DisplayBothClocks();
4741     else
4742       StartClocks();
4743
4744     /* Display opponents and material strengths */
4745     if (gameInfo.variant != VariantBughouse &&
4746         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4747         if (tinyLayout || smallLayout) {
4748             if(gameInfo.variant == VariantNormal)
4749               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4750                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4751                     basetime, increment);
4752             else
4753               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4754                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4755                     basetime, increment, (int) gameInfo.variant);
4756         } else {
4757             if(gameInfo.variant == VariantNormal)
4758               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4759                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4760                     basetime, increment);
4761             else
4762               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4763                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4764                     basetime, increment, VariantName(gameInfo.variant));
4765         }
4766         DisplayTitle(str);
4767   if (appData.debugMode) {
4768     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4769   }
4770     }
4771
4772
4773     /* Display the board */
4774     if (!pausing && !appData.noGUI) {
4775
4776       if (appData.premove)
4777           if (!gotPremove ||
4778              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4779              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4780               ClearPremoveHighlights();
4781
4782       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4783         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4784       DrawPosition(j, boards[currentMove]);
4785
4786       DisplayMove(moveNum - 1);
4787       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4788             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4789               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4790         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4791       }
4792     }
4793
4794     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4795 #if ZIPPY
4796     if(bookHit) { // [HGM] book: simulate book reply
4797         static char bookMove[MSG_SIZ]; // a bit generous?
4798
4799         programStats.nodes = programStats.depth = programStats.time =
4800         programStats.score = programStats.got_only_move = 0;
4801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4802
4803         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4804         strcat(bookMove, bookHit);
4805         HandleMachineMove(bookMove, &first);
4806     }
4807 #endif
4808 }
4809
4810 void
4811 GetMoveListEvent()
4812 {
4813     char buf[MSG_SIZ];
4814     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4815         ics_getting_history = H_REQUESTED;
4816         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4817         SendToICS(buf);
4818     }
4819 }
4820
4821 void
4822 AnalysisPeriodicEvent(force)
4823      int force;
4824 {
4825     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4826          && !force) || !appData.periodicUpdates)
4827       return;
4828
4829     /* Send . command to Crafty to collect stats */
4830     SendToProgram(".\n", &first);
4831
4832     /* Don't send another until we get a response (this makes
4833        us stop sending to old Crafty's which don't understand
4834        the "." command (sending illegal cmds resets node count & time,
4835        which looks bad)) */
4836     programStats.ok_to_send = 0;
4837 }
4838
4839 void ics_update_width(new_width)
4840         int new_width;
4841 {
4842         ics_printf("set width %d\n", new_width);
4843 }
4844
4845 void
4846 SendMoveToProgram(moveNum, cps)
4847      int moveNum;
4848      ChessProgramState *cps;
4849 {
4850     char buf[MSG_SIZ];
4851
4852     if (cps->useUsermove) {
4853       SendToProgram("usermove ", cps);
4854     }
4855     if (cps->useSAN) {
4856       char *space;
4857       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4858         int len = space - parseList[moveNum];
4859         memcpy(buf, parseList[moveNum], len);
4860         buf[len++] = '\n';
4861         buf[len] = NULLCHAR;
4862       } else {
4863         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4864       }
4865       SendToProgram(buf, cps);
4866     } else {
4867       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4868         AlphaRank(moveList[moveNum], 4);
4869         SendToProgram(moveList[moveNum], cps);
4870         AlphaRank(moveList[moveNum], 4); // and back
4871       } else
4872       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4873        * the engine. It would be nice to have a better way to identify castle
4874        * moves here. */
4875       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4876                                                                          && cps->useOOCastle) {
4877         int fromX = moveList[moveNum][0] - AAA;
4878         int fromY = moveList[moveNum][1] - ONE;
4879         int toX = moveList[moveNum][2] - AAA;
4880         int toY = moveList[moveNum][3] - ONE;
4881         if((boards[moveNum][fromY][fromX] == WhiteKing
4882             && boards[moveNum][toY][toX] == WhiteRook)
4883            || (boards[moveNum][fromY][fromX] == BlackKing
4884                && boards[moveNum][toY][toX] == BlackRook)) {
4885           if(toX > fromX) SendToProgram("O-O\n", cps);
4886           else SendToProgram("O-O-O\n", cps);
4887         }
4888         else SendToProgram(moveList[moveNum], cps);
4889       }
4890       else SendToProgram(moveList[moveNum], cps);
4891       /* End of additions by Tord */
4892     }
4893
4894     /* [HGM] setting up the opening has brought engine in force mode! */
4895     /*       Send 'go' if we are in a mode where machine should play. */
4896     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4897         (gameMode == TwoMachinesPlay   ||
4898 #if ZIPPY
4899          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4900 #endif
4901          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4902         SendToProgram("go\n", cps);
4903   if (appData.debugMode) {
4904     fprintf(debugFP, "(extra)\n");
4905   }
4906     }
4907     setboardSpoiledMachineBlack = 0;
4908 }
4909
4910 void
4911 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4912      ChessMove moveType;
4913      int fromX, fromY, toX, toY;
4914      char promoChar;
4915 {
4916     char user_move[MSG_SIZ];
4917
4918     switch (moveType) {
4919       default:
4920         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4921                 (int)moveType, fromX, fromY, toX, toY);
4922         DisplayError(user_move + strlen("say "), 0);
4923         break;
4924       case WhiteKingSideCastle:
4925       case BlackKingSideCastle:
4926       case WhiteQueenSideCastleWild:
4927       case BlackQueenSideCastleWild:
4928       /* PUSH Fabien */
4929       case WhiteHSideCastleFR:
4930       case BlackHSideCastleFR:
4931       /* POP Fabien */
4932         snprintf(user_move, MSG_SIZ, "o-o\n");
4933         break;
4934       case WhiteQueenSideCastle:
4935       case BlackQueenSideCastle:
4936       case WhiteKingSideCastleWild:
4937       case BlackKingSideCastleWild:
4938       /* PUSH Fabien */
4939       case WhiteASideCastleFR:
4940       case BlackASideCastleFR:
4941       /* POP Fabien */
4942         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4943         break;
4944       case WhiteNonPromotion:
4945       case BlackNonPromotion:
4946         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4947         break;
4948       case WhitePromotion:
4949       case BlackPromotion:
4950         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4951           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4952                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4953                 PieceToChar(WhiteFerz));
4954         else if(gameInfo.variant == VariantGreat)
4955           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4956                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4957                 PieceToChar(WhiteMan));
4958         else
4959           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4960                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4961                 promoChar);
4962         break;
4963       case WhiteDrop:
4964       case BlackDrop:
4965       drop:
4966         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4967                  ToUpper(PieceToChar((ChessSquare) fromX)),
4968                  AAA + toX, ONE + toY);
4969         break;
4970       case IllegalMove:  /* could be a variant we don't quite understand */
4971         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4972       case NormalMove:
4973       case WhiteCapturesEnPassant:
4974       case BlackCapturesEnPassant:
4975         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4977         break;
4978     }
4979     SendToICS(user_move);
4980     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4981         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4982 }
4983
4984 void
4985 UploadGameEvent()
4986 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4987     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4988     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4989     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4990         DisplayError("You cannot do this while you are playing or observing", 0);
4991         return;
4992     }
4993     if(gameMode != IcsExamining) { // is this ever not the case?
4994         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4995
4996         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4997           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4998         } else { // on FICS we must first go to general examine mode
4999           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5000         }
5001         if(gameInfo.variant != VariantNormal) {
5002             // try figure out wild number, as xboard names are not always valid on ICS
5003             for(i=1; i<=36; i++) {
5004               snprintf(buf, MSG_SIZ, "wild/%d", i);
5005                 if(StringToVariant(buf) == gameInfo.variant) break;
5006             }
5007             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5008             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5009             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5010         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5011         SendToICS(ics_prefix);
5012         SendToICS(buf);
5013         if(startedFromSetupPosition || backwardMostMove != 0) {
5014           fen = PositionToFEN(backwardMostMove, NULL);
5015           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5016             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5017             SendToICS(buf);
5018           } else { // FICS: everything has to set by separate bsetup commands
5019             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5020             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5021             SendToICS(buf);
5022             if(!WhiteOnMove(backwardMostMove)) {
5023                 SendToICS("bsetup tomove black\n");
5024             }
5025             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5026             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5027             SendToICS(buf);
5028             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5029             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5030             SendToICS(buf);
5031             i = boards[backwardMostMove][EP_STATUS];
5032             if(i >= 0) { // set e.p.
5033               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5034                 SendToICS(buf);
5035             }
5036             bsetup++;
5037           }
5038         }
5039       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5040             SendToICS("bsetup done\n"); // switch to normal examining.
5041     }
5042     for(i = backwardMostMove; i<last; i++) {
5043         char buf[20];
5044         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5045         SendToICS(buf);
5046     }
5047     SendToICS(ics_prefix);
5048     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5049 }
5050
5051 void
5052 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5053      int rf, ff, rt, ft;
5054      char promoChar;
5055      char move[7];
5056 {
5057     if (rf == DROP_RANK) {
5058       sprintf(move, "%c@%c%c\n",
5059                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5060     } else {
5061         if (promoChar == 'x' || promoChar == NULLCHAR) {
5062           sprintf(move, "%c%c%c%c\n",
5063                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5064         } else {
5065             sprintf(move, "%c%c%c%c%c\n",
5066                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5067         }
5068     }
5069 }
5070
5071 void
5072 ProcessICSInitScript(f)
5073      FILE *f;
5074 {
5075     char buf[MSG_SIZ];
5076
5077     while (fgets(buf, MSG_SIZ, f)) {
5078         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5079     }
5080
5081     fclose(f);
5082 }
5083
5084
5085 static int lastX, lastY, selectFlag, dragging;
5086
5087 void
5088 Sweep(int step)
5089 {
5090     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5091     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5092     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5093     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5094     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5095     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5096     do {
5097         promoSweep -= step;
5098         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5099         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5100         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5101         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5102         if(!step) step = 1;
5103     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5104             appData.testLegality && (promoSweep == king ||
5105             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5106     ChangeDragPiece(promoSweep);
5107 }
5108
5109 int PromoScroll(int x, int y)
5110 {
5111   int step = 0;
5112
5113   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5114   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5115   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5116   if(!step) return FALSE;
5117   lastX = x; lastY = y;
5118   if((promoSweep < BlackPawn) == flipView) step = -step;
5119   if(step > 0) selectFlag = 1;
5120   if(!selectFlag) Sweep(step);
5121   return FALSE;
5122 }
5123
5124 void
5125 NextPiece(int step)
5126 {
5127     ChessSquare piece = boards[currentMove][toY][toX];
5128     do {
5129         pieceSweep -= step;
5130         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5131         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5132         if(!step) step = -1;
5133     } while(PieceToChar(pieceSweep) == '.');
5134     boards[currentMove][toY][toX] = pieceSweep;
5135     DrawPosition(FALSE, boards[currentMove]);
5136     boards[currentMove][toY][toX] = piece;
5137 }
5138 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5139 void
5140 AlphaRank(char *move, int n)
5141 {
5142 //    char *p = move, c; int x, y;
5143
5144     if (appData.debugMode) {
5145         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5146     }
5147
5148     if(move[1]=='*' &&
5149        move[2]>='0' && move[2]<='9' &&
5150        move[3]>='a' && move[3]<='x'    ) {
5151         move[1] = '@';
5152         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5153         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5154     } else
5155     if(move[0]>='0' && move[0]<='9' &&
5156        move[1]>='a' && move[1]<='x' &&
5157        move[2]>='0' && move[2]<='9' &&
5158        move[3]>='a' && move[3]<='x'    ) {
5159         /* input move, Shogi -> normal */
5160         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5161         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5162         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5163         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5164     } else
5165     if(move[1]=='@' &&
5166        move[3]>='0' && move[3]<='9' &&
5167        move[2]>='a' && move[2]<='x'    ) {
5168         move[1] = '*';
5169         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5170         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5171     } else
5172     if(
5173        move[0]>='a' && move[0]<='x' &&
5174        move[3]>='0' && move[3]<='9' &&
5175        move[2]>='a' && move[2]<='x'    ) {
5176          /* output move, normal -> Shogi */
5177         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5178         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5179         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5180         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5181         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5182     }
5183     if (appData.debugMode) {
5184         fprintf(debugFP, "   out = '%s'\n", move);
5185     }
5186 }
5187
5188 char yy_textstr[8000];
5189
5190 /* Parser for moves from gnuchess, ICS, or user typein box */
5191 Boolean
5192 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5193      char *move;
5194      int moveNum;
5195      ChessMove *moveType;
5196      int *fromX, *fromY, *toX, *toY;
5197      char *promoChar;
5198 {
5199     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5200
5201     switch (*moveType) {
5202       case WhitePromotion:
5203       case BlackPromotion:
5204       case WhiteNonPromotion:
5205       case BlackNonPromotion:
5206       case NormalMove:
5207       case WhiteCapturesEnPassant:
5208       case BlackCapturesEnPassant:
5209       case WhiteKingSideCastle:
5210       case WhiteQueenSideCastle:
5211       case BlackKingSideCastle:
5212       case BlackQueenSideCastle:
5213       case WhiteKingSideCastleWild:
5214       case WhiteQueenSideCastleWild:
5215       case BlackKingSideCastleWild:
5216       case BlackQueenSideCastleWild:
5217       /* Code added by Tord: */
5218       case WhiteHSideCastleFR:
5219       case WhiteASideCastleFR:
5220       case BlackHSideCastleFR:
5221       case BlackASideCastleFR:
5222       /* End of code added by Tord */
5223       case IllegalMove:         /* bug or odd chess variant */
5224         *fromX = currentMoveString[0] - AAA;
5225         *fromY = currentMoveString[1] - ONE;
5226         *toX = currentMoveString[2] - AAA;
5227         *toY = currentMoveString[3] - ONE;
5228         *promoChar = currentMoveString[4];
5229         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5230             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5231     if (appData.debugMode) {
5232         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5233     }
5234             *fromX = *fromY = *toX = *toY = 0;
5235             return FALSE;
5236         }
5237         if (appData.testLegality) {
5238           return (*moveType != IllegalMove);
5239         } else {
5240           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5241                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5242         }
5243
5244       case WhiteDrop:
5245       case BlackDrop:
5246         *fromX = *moveType == WhiteDrop ?
5247           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5248           (int) CharToPiece(ToLower(currentMoveString[0]));
5249         *fromY = DROP_RANK;
5250         *toX = currentMoveString[2] - AAA;
5251         *toY = currentMoveString[3] - ONE;
5252         *promoChar = NULLCHAR;
5253         return TRUE;
5254
5255       case AmbiguousMove:
5256       case ImpossibleMove:
5257       case EndOfFile:
5258       case ElapsedTime:
5259       case Comment:
5260       case PGNTag:
5261       case NAG:
5262       case WhiteWins:
5263       case BlackWins:
5264       case GameIsDrawn:
5265       default:
5266     if (appData.debugMode) {
5267         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5268     }
5269         /* bug? */
5270         *fromX = *fromY = *toX = *toY = 0;
5271         *promoChar = NULLCHAR;
5272         return FALSE;
5273     }
5274 }
5275
5276 Boolean pushed = FALSE;
5277
5278 void
5279 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5280 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5281   int fromX, fromY, toX, toY; char promoChar;
5282   ChessMove moveType;
5283   Boolean valid;
5284   int nr = 0;
5285
5286   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5287     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5288     pushed = TRUE;
5289   }
5290   endPV = forwardMostMove;
5291   do {
5292     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5293     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5294     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5295 if(appData.debugMode){
5296 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5297 }
5298     if(!valid && nr == 0 &&
5299        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5300         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5301         // Hande case where played move is different from leading PV move
5302         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5303         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5304         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5305         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5306           endPV += 2; // if position different, keep this
5307           moveList[endPV-1][0] = fromX + AAA;
5308           moveList[endPV-1][1] = fromY + ONE;
5309           moveList[endPV-1][2] = toX + AAA;
5310           moveList[endPV-1][3] = toY + ONE;
5311           parseList[endPV-1][0] = NULLCHAR;
5312           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5313         }
5314       }
5315     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5316     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5317     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5318     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5319         valid++; // allow comments in PV
5320         continue;
5321     }
5322     nr++;
5323     if(endPV+1 > framePtr) break; // no space, truncate
5324     if(!valid) break;
5325     endPV++;
5326     CopyBoard(boards[endPV], boards[endPV-1]);
5327     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5328     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5329     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5330     CoordsToAlgebraic(boards[endPV - 1],
5331                              PosFlags(endPV - 1),
5332                              fromY, fromX, toY, toX, promoChar,
5333                              parseList[endPV - 1]);
5334   } while(valid);
5335   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5336   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5337   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5338                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5339   DrawPosition(TRUE, boards[currentMove]);
5340 }
5341
5342 int
5343 MultiPV(ChessProgramState *cps)
5344 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5345         int i;
5346         for(i=0; i<cps->nrOptions; i++)
5347             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5348                 return i;
5349         return -1;
5350 }
5351
5352 Boolean
5353 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5354 {
5355         int startPV, multi, lineStart, origIndex = index;
5356         char *p, buf2[MSG_SIZ];
5357
5358         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5359         lastX = x; lastY = y;
5360         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5361         lineStart = startPV = index;
5362         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5363         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5364         index = startPV;
5365         do{ while(buf[index] && buf[index] != '\n') index++;
5366         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5367         buf[index] = 0;
5368         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5369                 int n = first.option[multi].value;
5370                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5371                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5372                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5373                 first.option[multi].value = n;
5374                 *start = *end = 0;
5375                 return FALSE;
5376         }
5377         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5378         *start = startPV; *end = index-1;
5379         return TRUE;
5380 }
5381
5382 Boolean
5383 LoadPV(int x, int y)
5384 { // called on right mouse click to load PV
5385   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5386   lastX = x; lastY = y;
5387   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5388   return TRUE;
5389 }
5390
5391 void
5392 UnLoadPV()
5393 {
5394   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5395   if(endPV < 0) return;
5396   endPV = -1;
5397   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5398         Boolean saveAnimate = appData.animate;
5399         if(pushed) {
5400             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5401                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5402             } else storedGames--; // abandon shelved tail of original game
5403         }
5404         pushed = FALSE;
5405         forwardMostMove = currentMove;
5406         currentMove = oldFMM;
5407         appData.animate = FALSE;
5408         ToNrEvent(forwardMostMove);
5409         appData.animate = saveAnimate;
5410   }
5411   currentMove = forwardMostMove;
5412   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5413   ClearPremoveHighlights();
5414   DrawPosition(TRUE, boards[currentMove]);
5415 }
5416
5417 void
5418 MovePV(int x, int y, int h)
5419 { // step through PV based on mouse coordinates (called on mouse move)
5420   int margin = h>>3, step = 0;
5421
5422   // we must somehow check if right button is still down (might be released off board!)
5423   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5424   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5425   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5426   if(!step) return;
5427   lastX = x; lastY = y;
5428
5429   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5430   if(endPV < 0) return;
5431   if(y < margin) step = 1; else
5432   if(y > h - margin) step = -1;
5433   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5434   currentMove += step;
5435   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5436   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5437                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5438   DrawPosition(FALSE, boards[currentMove]);
5439 }
5440
5441
5442 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5443 // All positions will have equal probability, but the current method will not provide a unique
5444 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5445 #define DARK 1
5446 #define LITE 2
5447 #define ANY 3
5448
5449 int squaresLeft[4];
5450 int piecesLeft[(int)BlackPawn];
5451 int seed, nrOfShuffles;
5452
5453 void GetPositionNumber()
5454 {       // sets global variable seed
5455         int i;
5456
5457         seed = appData.defaultFrcPosition;
5458         if(seed < 0) { // randomize based on time for negative FRC position numbers
5459                 for(i=0; i<50; i++) seed += random();
5460                 seed = random() ^ random() >> 8 ^ random() << 8;
5461                 if(seed<0) seed = -seed;
5462         }
5463 }
5464
5465 int put(Board board, int pieceType, int rank, int n, int shade)
5466 // put the piece on the (n-1)-th empty squares of the given shade
5467 {
5468         int i;
5469
5470         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5471                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5472                         board[rank][i] = (ChessSquare) pieceType;
5473                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5474                         squaresLeft[ANY]--;
5475                         piecesLeft[pieceType]--;
5476                         return i;
5477                 }
5478         }
5479         return -1;
5480 }
5481
5482
5483 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5484 // calculate where the next piece goes, (any empty square), and put it there
5485 {
5486         int i;
5487
5488         i = seed % squaresLeft[shade];
5489         nrOfShuffles *= squaresLeft[shade];
5490         seed /= squaresLeft[shade];
5491         put(board, pieceType, rank, i, shade);
5492 }
5493
5494 void AddTwoPieces(Board board, int pieceType, int rank)
5495 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5496 {
5497         int i, n=squaresLeft[ANY], j=n-1, k;
5498
5499         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5500         i = seed % k;  // pick one
5501         nrOfShuffles *= k;
5502         seed /= k;
5503         while(i >= j) i -= j--;
5504         j = n - 1 - j; i += j;
5505         put(board, pieceType, rank, j, ANY);
5506         put(board, pieceType, rank, i, ANY);
5507 }
5508
5509 void SetUpShuffle(Board board, int number)
5510 {
5511         int i, p, first=1;
5512
5513         GetPositionNumber(); nrOfShuffles = 1;
5514
5515         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5516         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5517         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5518
5519         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5520
5521         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5522             p = (int) board[0][i];
5523             if(p < (int) BlackPawn) piecesLeft[p] ++;
5524             board[0][i] = EmptySquare;
5525         }
5526
5527         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5528             // shuffles restricted to allow normal castling put KRR first
5529             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5530                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5531             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5532                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5533             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5534                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5535             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5536                 put(board, WhiteRook, 0, 0, ANY);
5537             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5538         }
5539
5540         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5541             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5542             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5543                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5544                 while(piecesLeft[p] >= 2) {
5545                     AddOnePiece(board, p, 0, LITE);
5546                     AddOnePiece(board, p, 0, DARK);
5547                 }
5548                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5549             }
5550
5551         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5552             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5553             // but we leave King and Rooks for last, to possibly obey FRC restriction
5554             if(p == (int)WhiteRook) continue;
5555             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5556             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5557         }
5558
5559         // now everything is placed, except perhaps King (Unicorn) and Rooks
5560
5561         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5562             // Last King gets castling rights
5563             while(piecesLeft[(int)WhiteUnicorn]) {
5564                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5565                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5566             }
5567
5568             while(piecesLeft[(int)WhiteKing]) {
5569                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5570                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5571             }
5572
5573
5574         } else {
5575             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5576             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5577         }
5578
5579         // Only Rooks can be left; simply place them all
5580         while(piecesLeft[(int)WhiteRook]) {
5581                 i = put(board, WhiteRook, 0, 0, ANY);
5582                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5583                         if(first) {
5584                                 first=0;
5585                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5586                         }
5587                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5588                 }
5589         }
5590         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5591             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5592         }
5593
5594         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5595 }
5596
5597 int SetCharTable( char *table, const char * map )
5598 /* [HGM] moved here from winboard.c because of its general usefulness */
5599 /*       Basically a safe strcpy that uses the last character as King */
5600 {
5601     int result = FALSE; int NrPieces;
5602
5603     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5604                     && NrPieces >= 12 && !(NrPieces&1)) {
5605         int i; /* [HGM] Accept even length from 12 to 34 */
5606
5607         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5608         for( i=0; i<NrPieces/2-1; i++ ) {
5609             table[i] = map[i];
5610             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5611         }
5612         table[(int) WhiteKing]  = map[NrPieces/2-1];
5613         table[(int) BlackKing]  = map[NrPieces-1];
5614
5615         result = TRUE;
5616     }
5617
5618     return result;
5619 }
5620
5621 void Prelude(Board board)
5622 {       // [HGM] superchess: random selection of exo-pieces
5623         int i, j, k; ChessSquare p;
5624         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5625
5626         GetPositionNumber(); // use FRC position number
5627
5628         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5629             SetCharTable(pieceToChar, appData.pieceToCharTable);
5630             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5631                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5632         }
5633
5634         j = seed%4;                 seed /= 4;
5635         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5636         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5637         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5638         j = seed%3 + (seed%3 >= j); seed /= 3;
5639         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5640         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5641         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5642         j = seed%3;                 seed /= 3;
5643         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5644         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5645         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5646         j = seed%2 + (seed%2 >= j); seed /= 2;
5647         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5648         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5649         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5650         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5651         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5652         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5653         put(board, exoPieces[0],    0, 0, ANY);
5654         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5655 }
5656
5657 void
5658 InitPosition(redraw)
5659      int redraw;
5660 {
5661     ChessSquare (* pieces)[BOARD_FILES];
5662     int i, j, pawnRow, overrule,
5663     oldx = gameInfo.boardWidth,
5664     oldy = gameInfo.boardHeight,
5665     oldh = gameInfo.holdingsWidth;
5666     static int oldv;
5667
5668     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5669
5670     /* [AS] Initialize pv info list [HGM] and game status */
5671     {
5672         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5673             pvInfoList[i].depth = 0;
5674             boards[i][EP_STATUS] = EP_NONE;
5675             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5676         }
5677
5678         initialRulePlies = 0; /* 50-move counter start */
5679
5680         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5681         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5682     }
5683
5684
5685     /* [HGM] logic here is completely changed. In stead of full positions */
5686     /* the initialized data only consist of the two backranks. The switch */
5687     /* selects which one we will use, which is than copied to the Board   */
5688     /* initialPosition, which for the rest is initialized by Pawns and    */
5689     /* empty squares. This initial position is then copied to boards[0],  */
5690     /* possibly after shuffling, so that it remains available.            */
5691
5692     gameInfo.holdingsWidth = 0; /* default board sizes */
5693     gameInfo.boardWidth    = 8;
5694     gameInfo.boardHeight   = 8;
5695     gameInfo.holdingsSize  = 0;
5696     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5697     for(i=0; i<BOARD_FILES-2; i++)
5698       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5699     initialPosition[EP_STATUS] = EP_NONE;
5700     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5701     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5702          SetCharTable(pieceNickName, appData.pieceNickNames);
5703     else SetCharTable(pieceNickName, "............");
5704     pieces = FIDEArray;
5705
5706     switch (gameInfo.variant) {
5707     case VariantFischeRandom:
5708       shuffleOpenings = TRUE;
5709     default:
5710       break;
5711     case VariantShatranj:
5712       pieces = ShatranjArray;
5713       nrCastlingRights = 0;
5714       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5715       break;
5716     case VariantMakruk:
5717       pieces = makrukArray;
5718       nrCastlingRights = 0;
5719       startedFromSetupPosition = TRUE;
5720       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5721       break;
5722     case VariantTwoKings:
5723       pieces = twoKingsArray;
5724       break;
5725     case VariantCapaRandom:
5726       shuffleOpenings = TRUE;
5727     case VariantCapablanca:
5728       pieces = CapablancaArray;
5729       gameInfo.boardWidth = 10;
5730       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5731       break;
5732     case VariantGothic:
5733       pieces = GothicArray;
5734       gameInfo.boardWidth = 10;
5735       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5736       break;
5737     case VariantSChess:
5738       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5739       gameInfo.holdingsSize = 7;
5740       break;
5741     case VariantJanus:
5742       pieces = JanusArray;
5743       gameInfo.boardWidth = 10;
5744       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5745       nrCastlingRights = 6;
5746         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5747         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5748         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5749         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5750         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5751         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5752       break;
5753     case VariantFalcon:
5754       pieces = FalconArray;
5755       gameInfo.boardWidth = 10;
5756       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5757       break;
5758     case VariantXiangqi:
5759       pieces = XiangqiArray;
5760       gameInfo.boardWidth  = 9;
5761       gameInfo.boardHeight = 10;
5762       nrCastlingRights = 0;
5763       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5764       break;
5765     case VariantShogi:
5766       pieces = ShogiArray;
5767       gameInfo.boardWidth  = 9;
5768       gameInfo.boardHeight = 9;
5769       gameInfo.holdingsSize = 7;
5770       nrCastlingRights = 0;
5771       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5772       break;
5773     case VariantCourier:
5774       pieces = CourierArray;
5775       gameInfo.boardWidth  = 12;
5776       nrCastlingRights = 0;
5777       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5778       break;
5779     case VariantKnightmate:
5780       pieces = KnightmateArray;
5781       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5782       break;
5783     case VariantSpartan:
5784       pieces = SpartanArray;
5785       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5786       break;
5787     case VariantFairy:
5788       pieces = fairyArray;
5789       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5790       break;
5791     case VariantGreat:
5792       pieces = GreatArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5795       gameInfo.holdingsSize = 8;
5796       break;
5797     case VariantSuper:
5798       pieces = FIDEArray;
5799       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5800       gameInfo.holdingsSize = 8;
5801       startedFromSetupPosition = TRUE;
5802       break;
5803     case VariantCrazyhouse:
5804     case VariantBughouse:
5805       pieces = FIDEArray;
5806       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5807       gameInfo.holdingsSize = 5;
5808       break;
5809     case VariantWildCastle:
5810       pieces = FIDEArray;
5811       /* !!?shuffle with kings guaranteed to be on d or e file */
5812       shuffleOpenings = 1;
5813       break;
5814     case VariantNoCastle:
5815       pieces = FIDEArray;
5816       nrCastlingRights = 0;
5817       /* !!?unconstrained back-rank shuffle */
5818       shuffleOpenings = 1;
5819       break;
5820     }
5821
5822     overrule = 0;
5823     if(appData.NrFiles >= 0) {
5824         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5825         gameInfo.boardWidth = appData.NrFiles;
5826     }
5827     if(appData.NrRanks >= 0) {
5828         gameInfo.boardHeight = appData.NrRanks;
5829     }
5830     if(appData.holdingsSize >= 0) {
5831         i = appData.holdingsSize;
5832         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5833         gameInfo.holdingsSize = i;
5834     }
5835     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5836     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5837         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5838
5839     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5840     if(pawnRow < 1) pawnRow = 1;
5841     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5842
5843     /* User pieceToChar list overrules defaults */
5844     if(appData.pieceToCharTable != NULL)
5845         SetCharTable(pieceToChar, appData.pieceToCharTable);
5846
5847     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5848
5849         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5850             s = (ChessSquare) 0; /* account holding counts in guard band */
5851         for( i=0; i<BOARD_HEIGHT; i++ )
5852             initialPosition[i][j] = s;
5853
5854         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5855         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5856         initialPosition[pawnRow][j] = WhitePawn;
5857         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5858         if(gameInfo.variant == VariantXiangqi) {
5859             if(j&1) {
5860                 initialPosition[pawnRow][j] =
5861                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5862                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5863                    initialPosition[2][j] = WhiteCannon;
5864                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5865                 }
5866             }
5867         }
5868         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5869     }
5870     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5871
5872             j=BOARD_LEFT+1;
5873             initialPosition[1][j] = WhiteBishop;
5874             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5875             j=BOARD_RGHT-2;
5876             initialPosition[1][j] = WhiteRook;
5877             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5878     }
5879
5880     if( nrCastlingRights == -1) {
5881         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5882         /*       This sets default castling rights from none to normal corners   */
5883         /* Variants with other castling rights must set them themselves above    */
5884         nrCastlingRights = 6;
5885
5886         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5887         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5888         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5889         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5890         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5891         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5892      }
5893
5894      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5895      if(gameInfo.variant == VariantGreat) { // promotion commoners
5896         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5897         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5898         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5899         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5900      }
5901      if( gameInfo.variant == VariantSChess ) {
5902       initialPosition[1][0] = BlackMarshall;
5903       initialPosition[2][0] = BlackAngel;
5904       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5905       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5906       initialPosition[1][1] = initialPosition[2][1] = 
5907       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5908      }
5909   if (appData.debugMode) {
5910     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5911   }
5912     if(shuffleOpenings) {
5913         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5914         startedFromSetupPosition = TRUE;
5915     }
5916     if(startedFromPositionFile) {
5917       /* [HGM] loadPos: use PositionFile for every new game */
5918       CopyBoard(initialPosition, filePosition);
5919       for(i=0; i<nrCastlingRights; i++)
5920           initialRights[i] = filePosition[CASTLING][i];
5921       startedFromSetupPosition = TRUE;
5922     }
5923
5924     CopyBoard(boards[0], initialPosition);
5925
5926     if(oldx != gameInfo.boardWidth ||
5927        oldy != gameInfo.boardHeight ||
5928        oldv != gameInfo.variant ||
5929        oldh != gameInfo.holdingsWidth
5930                                          )
5931             InitDrawingSizes(-2 ,0);
5932
5933     oldv = gameInfo.variant;
5934     if (redraw)
5935       DrawPosition(TRUE, boards[currentMove]);
5936 }
5937
5938 void
5939 SendBoard(cps, moveNum)
5940      ChessProgramState *cps;
5941      int moveNum;
5942 {
5943     char message[MSG_SIZ];
5944
5945     if (cps->useSetboard) {
5946       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5947       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5948       SendToProgram(message, cps);
5949       free(fen);
5950
5951     } else {
5952       ChessSquare *bp;
5953       int i, j;
5954       /* Kludge to set black to move, avoiding the troublesome and now
5955        * deprecated "black" command.
5956        */
5957       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5958         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5959
5960       SendToProgram("edit\n", cps);
5961       SendToProgram("#\n", cps);
5962       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5963         bp = &boards[moveNum][i][BOARD_LEFT];
5964         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5965           if ((int) *bp < (int) BlackPawn) {
5966             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5967                     AAA + j, ONE + i);
5968             if(message[0] == '+' || message[0] == '~') {
5969               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5970                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5971                         AAA + j, ONE + i);
5972             }
5973             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5974                 message[1] = BOARD_RGHT   - 1 - j + '1';
5975                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5976             }
5977             SendToProgram(message, cps);
5978           }
5979         }
5980       }
5981
5982       SendToProgram("c\n", cps);
5983       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5984         bp = &boards[moveNum][i][BOARD_LEFT];
5985         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5986           if (((int) *bp != (int) EmptySquare)
5987               && ((int) *bp >= (int) BlackPawn)) {
5988             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5989                     AAA + j, ONE + i);
5990             if(message[0] == '+' || message[0] == '~') {
5991               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5992                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5993                         AAA + j, ONE + i);
5994             }
5995             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5996                 message[1] = BOARD_RGHT   - 1 - j + '1';
5997                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5998             }
5999             SendToProgram(message, cps);
6000           }
6001         }
6002       }
6003
6004       SendToProgram(".\n", cps);
6005     }
6006     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6007 }
6008
6009 ChessSquare
6010 DefaultPromoChoice(int white)
6011 {
6012     ChessSquare result;
6013     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6014         result = WhiteFerz; // no choice
6015     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6016         result= WhiteKing; // in Suicide Q is the last thing we want
6017     else if(gameInfo.variant == VariantSpartan)
6018         result = white ? WhiteQueen : WhiteAngel;
6019     else result = WhiteQueen;
6020     if(!white) result = WHITE_TO_BLACK result;
6021     return result;
6022 }
6023
6024 static int autoQueen; // [HGM] oneclick
6025
6026 int
6027 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6028 {
6029     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6030     /* [HGM] add Shogi promotions */
6031     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6032     ChessSquare piece;
6033     ChessMove moveType;
6034     Boolean premove;
6035
6036     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6037     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6038
6039     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6040       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6041         return FALSE;
6042
6043     piece = boards[currentMove][fromY][fromX];
6044     if(gameInfo.variant == VariantShogi) {
6045         promotionZoneSize = BOARD_HEIGHT/3;
6046         highestPromotingPiece = (int)WhiteFerz;
6047     } else if(gameInfo.variant == VariantMakruk) {
6048         promotionZoneSize = 3;
6049     }
6050
6051     // Treat Lance as Pawn when it is not representing Amazon
6052     if(gameInfo.variant != VariantSuper) {
6053         if(piece == WhiteLance) piece = WhitePawn; else
6054         if(piece == BlackLance) piece = BlackPawn;
6055     }
6056
6057     // next weed out all moves that do not touch the promotion zone at all
6058     if((int)piece >= BlackPawn) {
6059         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6060              return FALSE;
6061         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6062     } else {
6063         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6064            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6065     }
6066
6067     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6068
6069     // weed out mandatory Shogi promotions
6070     if(gameInfo.variant == VariantShogi) {
6071         if(piece >= BlackPawn) {
6072             if(toY == 0 && piece == BlackPawn ||
6073                toY == 0 && piece == BlackQueen ||
6074                toY <= 1 && piece == BlackKnight) {
6075                 *promoChoice = '+';
6076                 return FALSE;
6077             }
6078         } else {
6079             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6080                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6081                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6082                 *promoChoice = '+';
6083                 return FALSE;
6084             }
6085         }
6086     }
6087
6088     // weed out obviously illegal Pawn moves
6089     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6090         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6091         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6092         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6093         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6094         // note we are not allowed to test for valid (non-)capture, due to premove
6095     }
6096
6097     // we either have a choice what to promote to, or (in Shogi) whether to promote
6098     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6099         *promoChoice = PieceToChar(BlackFerz);  // no choice
6100         return FALSE;
6101     }
6102     // no sense asking what we must promote to if it is going to explode...
6103     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6104         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6105         return FALSE;
6106     }
6107     // give caller the default choice even if we will not make it
6108     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6109     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6110     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6111                            && gameInfo.variant != VariantShogi
6112                            && gameInfo.variant != VariantSuper) return FALSE;
6113     if(autoQueen) return FALSE; // predetermined
6114
6115     // suppress promotion popup on illegal moves that are not premoves
6116     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6117               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6118     if(appData.testLegality && !premove) {
6119         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6120                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6121         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6122             return FALSE;
6123     }
6124
6125     return TRUE;
6126 }
6127
6128 int
6129 InPalace(row, column)
6130      int row, column;
6131 {   /* [HGM] for Xiangqi */
6132     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6133          column < (BOARD_WIDTH + 4)/2 &&
6134          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6135     return FALSE;
6136 }
6137
6138 int
6139 PieceForSquare (x, y)
6140      int x;
6141      int y;
6142 {
6143   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6144      return -1;
6145   else
6146      return boards[currentMove][y][x];
6147 }
6148
6149 int
6150 OKToStartUserMove(x, y)
6151      int x, y;
6152 {
6153     ChessSquare from_piece;
6154     int white_piece;
6155
6156     if (matchMode) return FALSE;
6157     if (gameMode == EditPosition) return TRUE;
6158
6159     if (x >= 0 && y >= 0)
6160       from_piece = boards[currentMove][y][x];
6161     else
6162       from_piece = EmptySquare;
6163
6164     if (from_piece == EmptySquare) return FALSE;
6165
6166     white_piece = (int)from_piece >= (int)WhitePawn &&
6167       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6168
6169     switch (gameMode) {
6170       case PlayFromGameFile:
6171       case AnalyzeFile:
6172       case TwoMachinesPlay:
6173       case EndOfGame:
6174         return FALSE;
6175
6176       case IcsObserving:
6177       case IcsIdle:
6178         return FALSE;
6179
6180       case MachinePlaysWhite:
6181       case IcsPlayingBlack:
6182         if (appData.zippyPlay) return FALSE;
6183         if (white_piece) {
6184             DisplayMoveError(_("You are playing Black"));
6185             return FALSE;
6186         }
6187         break;
6188
6189       case MachinePlaysBlack:
6190       case IcsPlayingWhite:
6191         if (appData.zippyPlay) return FALSE;
6192         if (!white_piece) {
6193             DisplayMoveError(_("You are playing White"));
6194             return FALSE;
6195         }
6196         break;
6197
6198       case EditGame:
6199         if (!white_piece && WhiteOnMove(currentMove)) {
6200             DisplayMoveError(_("It is White's turn"));
6201             return FALSE;
6202         }
6203         if (white_piece && !WhiteOnMove(currentMove)) {
6204             DisplayMoveError(_("It is Black's turn"));
6205             return FALSE;
6206         }
6207         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6208             /* Editing correspondence game history */
6209             /* Could disallow this or prompt for confirmation */
6210             cmailOldMove = -1;
6211         }
6212         break;
6213
6214       case BeginningOfGame:
6215         if (appData.icsActive) return FALSE;
6216         if (!appData.noChessProgram) {
6217             if (!white_piece) {
6218                 DisplayMoveError(_("You are playing White"));
6219                 return FALSE;
6220             }
6221         }
6222         break;
6223
6224       case Training:
6225         if (!white_piece && WhiteOnMove(currentMove)) {
6226             DisplayMoveError(_("It is White's turn"));
6227             return FALSE;
6228         }
6229         if (white_piece && !WhiteOnMove(currentMove)) {
6230             DisplayMoveError(_("It is Black's turn"));
6231             return FALSE;
6232         }
6233         break;
6234
6235       default:
6236       case IcsExamining:
6237         break;
6238     }
6239     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6240         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6241         && gameMode != AnalyzeFile && gameMode != Training) {
6242         DisplayMoveError(_("Displayed position is not current"));
6243         return FALSE;
6244     }
6245     return TRUE;
6246 }
6247
6248 Boolean
6249 OnlyMove(int *x, int *y, Boolean captures) {
6250     DisambiguateClosure cl;
6251     if (appData.zippyPlay) return FALSE;
6252     switch(gameMode) {
6253       case MachinePlaysBlack:
6254       case IcsPlayingWhite:
6255       case BeginningOfGame:
6256         if(!WhiteOnMove(currentMove)) return FALSE;
6257         break;
6258       case MachinePlaysWhite:
6259       case IcsPlayingBlack:
6260         if(WhiteOnMove(currentMove)) return FALSE;
6261         break;
6262       case EditGame:
6263         break;
6264       default:
6265         return FALSE;
6266     }
6267     cl.pieceIn = EmptySquare;
6268     cl.rfIn = *y;
6269     cl.ffIn = *x;
6270     cl.rtIn = -1;
6271     cl.ftIn = -1;
6272     cl.promoCharIn = NULLCHAR;
6273     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6274     if( cl.kind == NormalMove ||
6275         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6276         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6277         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6278       fromX = cl.ff;
6279       fromY = cl.rf;
6280       *x = cl.ft;
6281       *y = cl.rt;
6282       return TRUE;
6283     }
6284     if(cl.kind != ImpossibleMove) return FALSE;
6285     cl.pieceIn = EmptySquare;
6286     cl.rfIn = -1;
6287     cl.ffIn = -1;
6288     cl.rtIn = *y;
6289     cl.ftIn = *x;
6290     cl.promoCharIn = NULLCHAR;
6291     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6292     if( cl.kind == NormalMove ||
6293         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6294         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6295         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6296       fromX = cl.ff;
6297       fromY = cl.rf;
6298       *x = cl.ft;
6299       *y = cl.rt;
6300       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6301       return TRUE;
6302     }
6303     return FALSE;
6304 }
6305
6306 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6307 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6308 int lastLoadGameUseList = FALSE;
6309 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6310 ChessMove lastLoadGameStart = EndOfFile;
6311
6312 void
6313 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6314      int fromX, fromY, toX, toY;
6315      int promoChar;
6316 {
6317     ChessMove moveType;
6318     ChessSquare pdown, pup;
6319
6320     /* Check if the user is playing in turn.  This is complicated because we
6321        let the user "pick up" a piece before it is his turn.  So the piece he
6322        tried to pick up may have been captured by the time he puts it down!
6323        Therefore we use the color the user is supposed to be playing in this
6324        test, not the color of the piece that is currently on the starting
6325        square---except in EditGame mode, where the user is playing both
6326        sides; fortunately there the capture race can't happen.  (It can
6327        now happen in IcsExamining mode, but that's just too bad.  The user
6328        will get a somewhat confusing message in that case.)
6329        */
6330
6331     switch (gameMode) {
6332       case PlayFromGameFile:
6333       case AnalyzeFile:
6334       case TwoMachinesPlay:
6335       case EndOfGame:
6336       case IcsObserving:
6337       case IcsIdle:
6338         /* We switched into a game mode where moves are not accepted,
6339            perhaps while the mouse button was down. */
6340         return;
6341
6342       case MachinePlaysWhite:
6343         /* User is moving for Black */
6344         if (WhiteOnMove(currentMove)) {
6345             DisplayMoveError(_("It is White's turn"));
6346             return;
6347         }
6348         break;
6349
6350       case MachinePlaysBlack:
6351         /* User is moving for White */
6352         if (!WhiteOnMove(currentMove)) {
6353             DisplayMoveError(_("It is Black's turn"));
6354             return;
6355         }
6356         break;
6357
6358       case EditGame:
6359       case IcsExamining:
6360       case BeginningOfGame:
6361       case AnalyzeMode:
6362       case Training:
6363         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6364         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6365             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6366             /* User is moving for Black */
6367             if (WhiteOnMove(currentMove)) {
6368                 DisplayMoveError(_("It is White's turn"));
6369                 return;
6370             }
6371         } else {
6372             /* User is moving for White */
6373             if (!WhiteOnMove(currentMove)) {
6374                 DisplayMoveError(_("It is Black's turn"));
6375                 return;
6376             }
6377         }
6378         break;
6379
6380       case IcsPlayingBlack:
6381         /* User is moving for Black */
6382         if (WhiteOnMove(currentMove)) {
6383             if (!appData.premove) {
6384                 DisplayMoveError(_("It is White's turn"));
6385             } else if (toX >= 0 && toY >= 0) {
6386                 premoveToX = toX;
6387                 premoveToY = toY;
6388                 premoveFromX = fromX;
6389                 premoveFromY = fromY;
6390                 premovePromoChar = promoChar;
6391                 gotPremove = 1;
6392                 if (appData.debugMode)
6393                     fprintf(debugFP, "Got premove: fromX %d,"
6394                             "fromY %d, toX %d, toY %d\n",
6395                             fromX, fromY, toX, toY);
6396             }
6397             return;
6398         }
6399         break;
6400
6401       case IcsPlayingWhite:
6402         /* User is moving for White */
6403         if (!WhiteOnMove(currentMove)) {
6404             if (!appData.premove) {
6405                 DisplayMoveError(_("It is Black's turn"));
6406             } else if (toX >= 0 && toY >= 0) {
6407                 premoveToX = toX;
6408                 premoveToY = toY;
6409                 premoveFromX = fromX;
6410                 premoveFromY = fromY;
6411                 premovePromoChar = promoChar;
6412                 gotPremove = 1;
6413                 if (appData.debugMode)
6414                     fprintf(debugFP, "Got premove: fromX %d,"
6415                             "fromY %d, toX %d, toY %d\n",
6416                             fromX, fromY, toX, toY);
6417             }
6418             return;
6419         }
6420         break;
6421
6422       default:
6423         break;
6424
6425       case EditPosition:
6426         /* EditPosition, empty square, or different color piece;
6427            click-click move is possible */
6428         if (toX == -2 || toY == -2) {
6429             boards[0][fromY][fromX] = EmptySquare;
6430             DrawPosition(FALSE, boards[currentMove]);
6431             return;
6432         } else if (toX >= 0 && toY >= 0) {
6433             boards[0][toY][toX] = boards[0][fromY][fromX];
6434             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6435                 if(boards[0][fromY][0] != EmptySquare) {
6436                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6437                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6438                 }
6439             } else
6440             if(fromX == BOARD_RGHT+1) {
6441                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6442                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6443                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6444                 }
6445             } else
6446             boards[0][fromY][fromX] = EmptySquare;
6447             DrawPosition(FALSE, boards[currentMove]);
6448             return;
6449         }
6450         return;
6451     }
6452
6453     if(toX < 0 || toY < 0) return;
6454     pdown = boards[currentMove][fromY][fromX];
6455     pup = boards[currentMove][toY][toX];
6456
6457     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6458     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6459          if( pup != EmptySquare ) return;
6460          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6461            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6462                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6463            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6464            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6465            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6466            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6467          fromY = DROP_RANK;
6468     }
6469
6470     /* [HGM] always test for legality, to get promotion info */
6471     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6472                                          fromY, fromX, toY, toX, promoChar);
6473     /* [HGM] but possibly ignore an IllegalMove result */
6474     if (appData.testLegality) {
6475         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6476             DisplayMoveError(_("Illegal move"));
6477             return;
6478         }
6479     }
6480
6481     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6482 }
6483
6484 /* Common tail of UserMoveEvent and DropMenuEvent */
6485 int
6486 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6487      ChessMove moveType;
6488      int fromX, fromY, toX, toY;
6489      /*char*/int promoChar;
6490 {
6491     char *bookHit = 0;
6492
6493     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6494         // [HGM] superchess: suppress promotions to non-available piece
6495         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6496         if(WhiteOnMove(currentMove)) {
6497             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6498         } else {
6499             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6500         }
6501     }
6502
6503     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6504        move type in caller when we know the move is a legal promotion */
6505     if(moveType == NormalMove && promoChar)
6506         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6507
6508     /* [HGM] <popupFix> The following if has been moved here from
6509        UserMoveEvent(). Because it seemed to belong here (why not allow
6510        piece drops in training games?), and because it can only be
6511        performed after it is known to what we promote. */
6512     if (gameMode == Training) {
6513       /* compare the move played on the board to the next move in the
6514        * game. If they match, display the move and the opponent's response.
6515        * If they don't match, display an error message.
6516        */
6517       int saveAnimate;
6518       Board testBoard;
6519       CopyBoard(testBoard, boards[currentMove]);
6520       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6521
6522       if (CompareBoards(testBoard, boards[currentMove+1])) {
6523         ForwardInner(currentMove+1);
6524
6525         /* Autoplay the opponent's response.
6526          * if appData.animate was TRUE when Training mode was entered,
6527          * the response will be animated.
6528          */
6529         saveAnimate = appData.animate;
6530         appData.animate = animateTraining;
6531         ForwardInner(currentMove+1);
6532         appData.animate = saveAnimate;
6533
6534         /* check for the end of the game */
6535         if (currentMove >= forwardMostMove) {
6536           gameMode = PlayFromGameFile;
6537           ModeHighlight();
6538           SetTrainingModeOff();
6539           DisplayInformation(_("End of game"));
6540         }
6541       } else {
6542         DisplayError(_("Incorrect move"), 0);
6543       }
6544       return 1;
6545     }
6546
6547   /* Ok, now we know that the move is good, so we can kill
6548      the previous line in Analysis Mode */
6549   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6550                                 && currentMove < forwardMostMove) {
6551     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6552     else forwardMostMove = currentMove;
6553   }
6554
6555   /* If we need the chess program but it's dead, restart it */
6556   ResurrectChessProgram();
6557
6558   /* A user move restarts a paused game*/
6559   if (pausing)
6560     PauseEvent();
6561
6562   thinkOutput[0] = NULLCHAR;
6563
6564   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6565
6566   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6567     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6568     return 1;
6569   }
6570
6571   if (gameMode == BeginningOfGame) {
6572     if (appData.noChessProgram) {
6573       gameMode = EditGame;
6574       SetGameInfo();
6575     } else {
6576       char buf[MSG_SIZ];
6577       gameMode = MachinePlaysBlack;
6578       StartClocks();
6579       SetGameInfo();
6580       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6581       DisplayTitle(buf);
6582       if (first.sendName) {
6583         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6584         SendToProgram(buf, &first);
6585       }
6586       StartClocks();
6587     }
6588     ModeHighlight();
6589   }
6590
6591   /* Relay move to ICS or chess engine */
6592   if (appData.icsActive) {
6593     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6594         gameMode == IcsExamining) {
6595       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6596         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6597         SendToICS("draw ");
6598         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6599       }
6600       // also send plain move, in case ICS does not understand atomic claims
6601       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6602       ics_user_moved = 1;
6603     }
6604   } else {
6605     if (first.sendTime && (gameMode == BeginningOfGame ||
6606                            gameMode == MachinePlaysWhite ||
6607                            gameMode == MachinePlaysBlack)) {
6608       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6609     }
6610     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6611          // [HGM] book: if program might be playing, let it use book
6612         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6613         first.maybeThinking = TRUE;
6614     } else SendMoveToProgram(forwardMostMove-1, &first);
6615     if (currentMove == cmailOldMove + 1) {
6616       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6617     }
6618   }
6619
6620   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6621
6622   switch (gameMode) {
6623   case EditGame:
6624     if(appData.testLegality)
6625     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6626     case MT_NONE:
6627     case MT_CHECK:
6628       break;
6629     case MT_CHECKMATE:
6630     case MT_STAINMATE:
6631       if (WhiteOnMove(currentMove)) {
6632         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6633       } else {
6634         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6635       }
6636       break;
6637     case MT_STALEMATE:
6638       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6639       break;
6640     }
6641     break;
6642
6643   case MachinePlaysBlack:
6644   case MachinePlaysWhite:
6645     /* disable certain menu options while machine is thinking */
6646     SetMachineThinkingEnables();
6647     break;
6648
6649   default:
6650     break;
6651   }
6652
6653   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6654   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6655
6656   if(bookHit) { // [HGM] book: simulate book reply
6657         static char bookMove[MSG_SIZ]; // a bit generous?
6658
6659         programStats.nodes = programStats.depth = programStats.time =
6660         programStats.score = programStats.got_only_move = 0;
6661         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6662
6663         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6664         strcat(bookMove, bookHit);
6665         HandleMachineMove(bookMove, &first);
6666   }
6667   return 1;
6668 }
6669
6670 void
6671 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6672      Board board;
6673      int flags;
6674      ChessMove kind;
6675      int rf, ff, rt, ft;
6676      VOIDSTAR closure;
6677 {
6678     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6679     Markers *m = (Markers *) closure;
6680     if(rf == fromY && ff == fromX)
6681         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6682                          || kind == WhiteCapturesEnPassant
6683                          || kind == BlackCapturesEnPassant);
6684     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6685 }
6686
6687 void
6688 MarkTargetSquares(int clear)
6689 {
6690   int x, y;
6691   if(!appData.markers || !appData.highlightDragging ||
6692      !appData.testLegality || gameMode == EditPosition) return;
6693   if(clear) {
6694     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6695   } else {
6696     int capt = 0;
6697     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6698     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6699       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6700       if(capt)
6701       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6702     }
6703   }
6704   DrawPosition(TRUE, NULL);
6705 }
6706
6707 int
6708 Explode(Board board, int fromX, int fromY, int toX, int toY)
6709 {
6710     if(gameInfo.variant == VariantAtomic &&
6711        (board[toY][toX] != EmptySquare ||                     // capture?
6712         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6713                          board[fromY][fromX] == BlackPawn   )
6714       )) {
6715         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6716         return TRUE;
6717     }
6718     return FALSE;
6719 }
6720
6721 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6722
6723 int CanPromote(ChessSquare piece, int y)
6724 {
6725         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6726         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6727         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6728            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6729            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6730                                                   gameInfo.variant == VariantMakruk) return FALSE;
6731         return (piece == BlackPawn && y == 1 ||
6732                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6733                 piece == BlackLance && y == 1 ||
6734                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6735 }
6736
6737 void LeftClick(ClickType clickType, int xPix, int yPix)
6738 {
6739     int x, y;
6740     Boolean saveAnimate;
6741     static int second = 0, promotionChoice = 0, clearFlag = 0;
6742     char promoChoice = NULLCHAR;
6743     ChessSquare piece;
6744
6745     if(appData.seekGraph && appData.icsActive && loggedOn &&
6746         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6747         SeekGraphClick(clickType, xPix, yPix, 0);
6748         return;
6749     }
6750
6751     if (clickType == Press) ErrorPopDown();
6752     MarkTargetSquares(1);
6753
6754     x = EventToSquare(xPix, BOARD_WIDTH);
6755     y = EventToSquare(yPix, BOARD_HEIGHT);
6756     if (!flipView && y >= 0) {
6757         y = BOARD_HEIGHT - 1 - y;
6758     }
6759     if (flipView && x >= 0) {
6760         x = BOARD_WIDTH - 1 - x;
6761     }
6762
6763     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6764         defaultPromoChoice = promoSweep;
6765         promoSweep = EmptySquare;   // terminate sweep
6766         promoDefaultAltered = TRUE;
6767         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6768     }
6769
6770     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6771         if(clickType == Release) return; // ignore upclick of click-click destination
6772         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6773         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6774         if(gameInfo.holdingsWidth &&
6775                 (WhiteOnMove(currentMove)
6776                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6777                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6778             // click in right holdings, for determining promotion piece
6779             ChessSquare p = boards[currentMove][y][x];
6780             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6781             if(p != EmptySquare) {
6782                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6783                 fromX = fromY = -1;
6784                 return;
6785             }
6786         }
6787         DrawPosition(FALSE, boards[currentMove]);
6788         return;
6789     }
6790
6791     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6792     if(clickType == Press
6793             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6794               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6795               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6796         return;
6797
6798     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6799         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6800
6801     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6802         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6803                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6804         defaultPromoChoice = DefaultPromoChoice(side);
6805     }
6806
6807     autoQueen = appData.alwaysPromoteToQueen;
6808
6809     if (fromX == -1) {
6810       int originalY = y;
6811       gatingPiece = EmptySquare;
6812       if (clickType != Press) {
6813         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6814             DragPieceEnd(xPix, yPix); dragging = 0;
6815             DrawPosition(FALSE, NULL);
6816         }
6817         return;
6818       }
6819       fromX = x; fromY = y;
6820       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6821          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6822          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6823             /* First square */
6824             if (OKToStartUserMove(fromX, fromY)) {
6825                 second = 0;
6826                 MarkTargetSquares(0);
6827                 DragPieceBegin(xPix, yPix); dragging = 1;
6828                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6829                     promoSweep = defaultPromoChoice;
6830                     selectFlag = 0; lastX = xPix; lastY = yPix;
6831                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6832                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6833                 }
6834                 if (appData.highlightDragging) {
6835                     SetHighlights(fromX, fromY, -1, -1);
6836                 }
6837             } else fromX = fromY = -1;
6838             return;
6839         }
6840     }
6841
6842     /* fromX != -1 */
6843     if (clickType == Press && gameMode != EditPosition) {
6844         ChessSquare fromP;
6845         ChessSquare toP;
6846         int frc;
6847
6848         // ignore off-board to clicks
6849         if(y < 0 || x < 0) return;
6850
6851         /* Check if clicking again on the same color piece */
6852         fromP = boards[currentMove][fromY][fromX];
6853         toP = boards[currentMove][y][x];
6854         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6855         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6856              WhitePawn <= toP && toP <= WhiteKing &&
6857              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6858              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6859             (BlackPawn <= fromP && fromP <= BlackKing &&
6860              BlackPawn <= toP && toP <= BlackKing &&
6861              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6862              !(fromP == BlackKing && toP == BlackRook && frc))) {
6863             /* Clicked again on same color piece -- changed his mind */
6864             second = (x == fromX && y == fromY);
6865             promoDefaultAltered = FALSE;
6866            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6867             if (appData.highlightDragging) {
6868                 SetHighlights(x, y, -1, -1);
6869             } else {
6870                 ClearHighlights();
6871             }
6872             if (OKToStartUserMove(x, y)) {
6873                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6874                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6875                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6876                  gatingPiece = boards[currentMove][fromY][fromX];
6877                 else gatingPiece = EmptySquare;
6878                 fromX = x;
6879                 fromY = y; dragging = 1;
6880                 MarkTargetSquares(0);
6881                 DragPieceBegin(xPix, yPix);
6882                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6883                     promoSweep = defaultPromoChoice;
6884                     selectFlag = 0; lastX = xPix; lastY = yPix;
6885                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6886                 }
6887             }
6888            }
6889            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6890            second = FALSE; 
6891         }
6892         // ignore clicks on holdings
6893         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6894     }
6895
6896     if (clickType == Release && x == fromX && y == fromY) {
6897         DragPieceEnd(xPix, yPix); dragging = 0;
6898         if(clearFlag) {
6899             // a deferred attempt to click-click move an empty square on top of a piece
6900             boards[currentMove][y][x] = EmptySquare;
6901             ClearHighlights();
6902             DrawPosition(FALSE, boards[currentMove]);
6903             fromX = fromY = -1; clearFlag = 0;
6904             return;
6905         }
6906         if (appData.animateDragging) {
6907             /* Undo animation damage if any */
6908             DrawPosition(FALSE, NULL);
6909         }
6910         if (second) {
6911             /* Second up/down in same square; just abort move */
6912             second = 0;
6913             fromX = fromY = -1;
6914             gatingPiece = EmptySquare;
6915             ClearHighlights();
6916             gotPremove = 0;
6917             ClearPremoveHighlights();
6918         } else {
6919             /* First upclick in same square; start click-click mode */
6920             SetHighlights(x, y, -1, -1);
6921         }
6922         return;
6923     }
6924
6925     clearFlag = 0;
6926
6927     /* we now have a different from- and (possibly off-board) to-square */
6928     /* Completed move */
6929     toX = x;
6930     toY = y;
6931     saveAnimate = appData.animate;
6932     if (clickType == Press) {
6933         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6934             // must be Edit Position mode with empty-square selected
6935             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6936             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6937             return;
6938         }
6939         /* Finish clickclick move */
6940         if (appData.animate || appData.highlightLastMove) {
6941             SetHighlights(fromX, fromY, toX, toY);
6942         } else {
6943             ClearHighlights();
6944         }
6945     } else {
6946         /* Finish drag move */
6947         if (appData.highlightLastMove) {
6948             SetHighlights(fromX, fromY, toX, toY);
6949         } else {
6950             ClearHighlights();
6951         }
6952         DragPieceEnd(xPix, yPix); dragging = 0;
6953         /* Don't animate move and drag both */
6954         appData.animate = FALSE;
6955     }
6956
6957     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6958     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6959         ChessSquare piece = boards[currentMove][fromY][fromX];
6960         if(gameMode == EditPosition && piece != EmptySquare &&
6961            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6962             int n;
6963
6964             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6965                 n = PieceToNumber(piece - (int)BlackPawn);
6966                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6967                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6968                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6969             } else
6970             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6971                 n = PieceToNumber(piece);
6972                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6973                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6974                 boards[currentMove][n][BOARD_WIDTH-2]++;
6975             }
6976             boards[currentMove][fromY][fromX] = EmptySquare;
6977         }
6978         ClearHighlights();
6979         fromX = fromY = -1;
6980         DrawPosition(TRUE, boards[currentMove]);
6981         return;
6982     }
6983
6984     // off-board moves should not be highlighted
6985     if(x < 0 || y < 0) ClearHighlights();
6986
6987     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6988
6989     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6990         SetHighlights(fromX, fromY, toX, toY);
6991         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6992             // [HGM] super: promotion to captured piece selected from holdings
6993             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6994             promotionChoice = TRUE;
6995             // kludge follows to temporarily execute move on display, without promoting yet
6996             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6997             boards[currentMove][toY][toX] = p;
6998             DrawPosition(FALSE, boards[currentMove]);
6999             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7000             boards[currentMove][toY][toX] = q;
7001             DisplayMessage("Click in holdings to choose piece", "");
7002             return;
7003         }
7004         PromotionPopUp();
7005     } else {
7006         int oldMove = currentMove;
7007         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7008         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7009         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7010         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7011            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7012             DrawPosition(TRUE, boards[currentMove]);
7013         fromX = fromY = -1;
7014     }
7015     appData.animate = saveAnimate;
7016     if (appData.animate || appData.animateDragging) {
7017         /* Undo animation damage if needed */
7018         DrawPosition(FALSE, NULL);
7019     }
7020 }
7021
7022 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7023 {   // front-end-free part taken out of PieceMenuPopup
7024     int whichMenu; int xSqr, ySqr;
7025
7026     if(seekGraphUp) { // [HGM] seekgraph
7027         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7028         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7029         return -2;
7030     }
7031
7032     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7033          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7034         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7035         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7036         if(action == Press)   {
7037             originalFlip = flipView;
7038             flipView = !flipView; // temporarily flip board to see game from partners perspective
7039             DrawPosition(TRUE, partnerBoard);
7040             DisplayMessage(partnerStatus, "");
7041             partnerUp = TRUE;
7042         } else if(action == Release) {
7043             flipView = originalFlip;
7044             DrawPosition(TRUE, boards[currentMove]);
7045             partnerUp = FALSE;
7046         }
7047         return -2;
7048     }
7049
7050     xSqr = EventToSquare(x, BOARD_WIDTH);
7051     ySqr = EventToSquare(y, BOARD_HEIGHT);
7052     if (action == Release) {
7053         if(pieceSweep != EmptySquare) {
7054             EditPositionMenuEvent(pieceSweep, toX, toY);
7055             pieceSweep = EmptySquare;
7056         } else UnLoadPV(); // [HGM] pv
7057     }
7058     if (action != Press) return -2; // return code to be ignored
7059     switch (gameMode) {
7060       case IcsExamining:
7061         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7062       case EditPosition:
7063         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7064         if (xSqr < 0 || ySqr < 0) return -1;
7065         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7066         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7067         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7068         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7069         NextPiece(0);
7070         return -2;\r
7071       case IcsObserving:
7072         if(!appData.icsEngineAnalyze) return -1;
7073       case IcsPlayingWhite:
7074       case IcsPlayingBlack:
7075         if(!appData.zippyPlay) goto noZip;
7076       case AnalyzeMode:
7077       case AnalyzeFile:
7078       case MachinePlaysWhite:
7079       case MachinePlaysBlack:
7080       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7081         if (!appData.dropMenu) {
7082           LoadPV(x, y);
7083           return 2; // flag front-end to grab mouse events
7084         }
7085         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7086            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7087       case EditGame:
7088       noZip:
7089         if (xSqr < 0 || ySqr < 0) return -1;
7090         if (!appData.dropMenu || appData.testLegality &&
7091             gameInfo.variant != VariantBughouse &&
7092             gameInfo.variant != VariantCrazyhouse) return -1;
7093         whichMenu = 1; // drop menu
7094         break;
7095       default:
7096         return -1;
7097     }
7098
7099     if (((*fromX = xSqr) < 0) ||
7100         ((*fromY = ySqr) < 0)) {
7101         *fromX = *fromY = -1;
7102         return -1;
7103     }
7104     if (flipView)
7105       *fromX = BOARD_WIDTH - 1 - *fromX;
7106     else
7107       *fromY = BOARD_HEIGHT - 1 - *fromY;
7108
7109     return whichMenu;
7110 }
7111
7112 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7113 {
7114 //    char * hint = lastHint;
7115     FrontEndProgramStats stats;
7116
7117     stats.which = cps == &first ? 0 : 1;
7118     stats.depth = cpstats->depth;
7119     stats.nodes = cpstats->nodes;
7120     stats.score = cpstats->score;
7121     stats.time = cpstats->time;
7122     stats.pv = cpstats->movelist;
7123     stats.hint = lastHint;
7124     stats.an_move_index = 0;
7125     stats.an_move_count = 0;
7126
7127     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7128         stats.hint = cpstats->move_name;
7129         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7130         stats.an_move_count = cpstats->nr_moves;
7131     }
7132
7133     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7134
7135     SetProgramStats( &stats );
7136 }
7137
7138 #define MAXPLAYERS 500
7139
7140 char *
7141 TourneyStandings(int display)
7142 {
7143     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7144     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7145     char result, *p, *names[MAXPLAYERS];
7146
7147     names[0] = p = strdup(appData.participants);
7148     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7149
7150     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7151
7152     while(result = appData.results[nr]) {
7153         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7154         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7155         wScore = bScore = 0;
7156         switch(result) {
7157           case '+': wScore = 2; break;
7158           case '-': bScore = 2; break;
7159           case '=': wScore = bScore = 1; break;
7160           case ' ':
7161           case '*': return strdup("busy"); // tourney not finished
7162         }
7163         score[w] += wScore;
7164         score[b] += bScore;
7165         games[w]++;
7166         games[b]++;
7167         nr++;
7168     }
7169     if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7170     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7171     for(w=0; w<nPlayers; w++) {
7172         bScore = -1;
7173         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7174         ranking[w] = b; points[w] = bScore; score[b] = -2;
7175     }
7176     p = malloc(nPlayers*34+1);
7177     for(w=0; w<nPlayers && w<display; w++)
7178         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7179     free(names[0]);
7180     return p;
7181 }
7182
7183 void
7184 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7185 {       // count all piece types
7186         int p, f, r;
7187         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7188         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7189         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7190                 p = board[r][f];
7191                 pCnt[p]++;
7192                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7193                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7194                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7195                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7196                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7197                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7198         }
7199 }
7200
7201 int
7202 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7203 {
7204         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7205         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7206
7207         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7208         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7209         if(myPawns == 2 && nMine == 3) // KPP
7210             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7211         if(myPawns == 1 && nMine == 2) // KP
7212             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7213         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7214             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7215         if(myPawns) return FALSE;
7216         if(pCnt[WhiteRook+side])
7217             return pCnt[BlackRook-side] ||
7218                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7219                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7220                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7221         if(pCnt[WhiteCannon+side]) {
7222             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7223             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7224         }
7225         if(pCnt[WhiteKnight+side])
7226             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7227         return FALSE;
7228 }
7229
7230 int
7231 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7232 {
7233         VariantClass v = gameInfo.variant;
7234
7235         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7236         if(v == VariantShatranj) return TRUE; // always winnable through baring
7237         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7238         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7239
7240         if(v == VariantXiangqi) {
7241                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7242
7243                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7244                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7245                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7246                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7247                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7248                 if(stale) // we have at least one last-rank P plus perhaps C
7249                     return majors // KPKX
7250                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7251                 else // KCA*E*
7252                     return pCnt[WhiteFerz+side] // KCAK
7253                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7254                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7255                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7256
7257         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7258                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7259
7260                 if(nMine == 1) return FALSE; // bare King
7261                 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
7262                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7263                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7264                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7265                 if(pCnt[WhiteKnight+side])
7266                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7267                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7268                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7269                 if(nBishops)
7270                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7271                 if(pCnt[WhiteAlfil+side])
7272                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7273                 if(pCnt[WhiteWazir+side])
7274                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7275         }
7276
7277         return TRUE;
7278 }
7279
7280 int
7281 Adjudicate(ChessProgramState *cps)
7282 {       // [HGM] some adjudications useful with buggy engines
7283         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7284         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7285         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7286         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7287         int k, count = 0; static int bare = 1;
7288         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7289         Boolean canAdjudicate = !appData.icsActive;
7290
7291         // most tests only when we understand the game, i.e. legality-checking on
7292             if( appData.testLegality )
7293             {   /* [HGM] Some more adjudications for obstinate engines */
7294                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7295                 static int moveCount = 6;
7296                 ChessMove result;
7297                 char *reason = NULL;
7298
7299                 /* Count what is on board. */
7300                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7301
7302                 /* Some material-based adjudications that have to be made before stalemate test */
7303                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7304                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7305                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7306                      if(canAdjudicate && appData.checkMates) {
7307                          if(engineOpponent)
7308                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7309                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7310                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7311                          return 1;
7312                      }
7313                 }
7314
7315                 /* Bare King in Shatranj (loses) or Losers (wins) */
7316                 if( nrW == 1 || nrB == 1) {
7317                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7318                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7319                      if(canAdjudicate && appData.checkMates) {
7320                          if(engineOpponent)
7321                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7322                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7323                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7324                          return 1;
7325                      }
7326                   } else
7327                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7328                   {    /* bare King */
7329                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7330                         if(canAdjudicate && appData.checkMates) {
7331                             /* but only adjudicate if adjudication enabled */
7332                             if(engineOpponent)
7333                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7334                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7335                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7336                             return 1;
7337                         }
7338                   }
7339                 } else bare = 1;
7340
7341
7342             // don't wait for engine to announce game end if we can judge ourselves
7343             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7344               case MT_CHECK:
7345                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7346                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7347                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7348                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7349                             checkCnt++;
7350                         if(checkCnt >= 2) {
7351                             reason = "Xboard adjudication: 3rd check";
7352                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7353                             break;
7354                         }
7355                     }
7356                 }
7357               case MT_NONE:
7358               default:
7359                 break;
7360               case MT_STALEMATE:
7361               case MT_STAINMATE:
7362                 reason = "Xboard adjudication: Stalemate";
7363                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7364                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7365                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7366                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7367                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7368                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7369                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7370                                                                         EP_CHECKMATE : EP_WINS);
7371                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7372                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7373                 }
7374                 break;
7375               case MT_CHECKMATE:
7376                 reason = "Xboard adjudication: Checkmate";
7377                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7378                 break;
7379             }
7380
7381                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7382                     case EP_STALEMATE:
7383                         result = GameIsDrawn; break;
7384                     case EP_CHECKMATE:
7385                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7386                     case EP_WINS:
7387                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7388                     default:
7389                         result = EndOfFile;
7390                 }
7391                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7392                     if(engineOpponent)
7393                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7394                     GameEnds( result, reason, GE_XBOARD );
7395                     return 1;
7396                 }
7397
7398                 /* Next absolutely insufficient mating material. */
7399                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7400                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7401                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7402
7403                      /* always flag draws, for judging claims */
7404                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7405
7406                      if(canAdjudicate && appData.materialDraws) {
7407                          /* but only adjudicate them if adjudication enabled */
7408                          if(engineOpponent) {
7409                            SendToProgram("force\n", engineOpponent); // suppress reply
7410                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7411                          }
7412                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7413                          return 1;
7414                      }
7415                 }
7416
7417                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7418                 if(gameInfo.variant == VariantXiangqi ?
7419                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7420                  : nrW + nrB == 4 &&
7421                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7422                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7423                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7424                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7425                    ) ) {
7426                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7427                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7428                           if(engineOpponent) {
7429                             SendToProgram("force\n", engineOpponent); // suppress reply
7430                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7431                           }
7432                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7433                           return 1;
7434                      }
7435                 } else moveCount = 6;
7436             }
7437         if (appData.debugMode) { int i;
7438             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7439                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7440                     appData.drawRepeats);
7441             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7442               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7443
7444         }
7445
7446         // Repetition draws and 50-move rule can be applied independently of legality testing
7447
7448                 /* Check for rep-draws */
7449                 count = 0;
7450                 for(k = forwardMostMove-2;
7451                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7452                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7453                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7454                     k-=2)
7455                 {   int rights=0;
7456                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7457                         /* compare castling rights */
7458                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7459                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7460                                 rights++; /* King lost rights, while rook still had them */
7461                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7462                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7463                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7464                                    rights++; /* but at least one rook lost them */
7465                         }
7466                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7467                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7468                                 rights++;
7469                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7470                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7471                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7472                                    rights++;
7473                         }
7474                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7475                             && appData.drawRepeats > 1) {
7476                              /* adjudicate after user-specified nr of repeats */
7477                              int result = GameIsDrawn;
7478                              char *details = "XBoard adjudication: repetition draw";
7479                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7480                                 // [HGM] xiangqi: check for forbidden perpetuals
7481                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7482                                 for(m=forwardMostMove; m>k; m-=2) {
7483                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7484                                         ourPerpetual = 0; // the current mover did not always check
7485                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7486                                         hisPerpetual = 0; // the opponent did not always check
7487                                 }
7488                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7489                                                                         ourPerpetual, hisPerpetual);
7490                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7491                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7492                                     details = "Xboard adjudication: perpetual checking";
7493                                 } else
7494                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7495                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7496                                 } else
7497                                 // Now check for perpetual chases
7498                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7499                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7500                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7501                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7502                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7503                                         details = "Xboard adjudication: perpetual chasing";
7504                                     } else
7505                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7506                                         break; // Abort repetition-checking loop.
7507                                 }
7508                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7509                              }
7510                              if(engineOpponent) {
7511                                SendToProgram("force\n", engineOpponent); // suppress reply
7512                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7513                              }
7514                              GameEnds( result, details, GE_XBOARD );
7515                              return 1;
7516                         }
7517                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7518                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7519                     }
7520                 }
7521
7522                 /* Now we test for 50-move draws. Determine ply count */
7523                 count = forwardMostMove;
7524                 /* look for last irreversble move */
7525                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7526                     count--;
7527                 /* if we hit starting position, add initial plies */
7528                 if( count == backwardMostMove )
7529                     count -= initialRulePlies;
7530                 count = forwardMostMove - count;
7531                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7532                         // adjust reversible move counter for checks in Xiangqi
7533                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7534                         if(i < backwardMostMove) i = backwardMostMove;
7535                         while(i <= forwardMostMove) {
7536                                 lastCheck = inCheck; // check evasion does not count
7537                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7538                                 if(inCheck || lastCheck) count--; // check does not count
7539                                 i++;
7540                         }
7541                 }
7542                 if( count >= 100)
7543                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7544                          /* this is used to judge if draw claims are legal */
7545                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7546                          if(engineOpponent) {
7547                            SendToProgram("force\n", engineOpponent); // suppress reply
7548                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7549                          }
7550                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7551                          return 1;
7552                 }
7553
7554                 /* if draw offer is pending, treat it as a draw claim
7555                  * when draw condition present, to allow engines a way to
7556                  * claim draws before making their move to avoid a race
7557                  * condition occurring after their move
7558                  */
7559                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7560                          char *p = NULL;
7561                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7562                              p = "Draw claim: 50-move rule";
7563                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7564                              p = "Draw claim: 3-fold repetition";
7565                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7566                              p = "Draw claim: insufficient mating material";
7567                          if( p != NULL && canAdjudicate) {
7568                              if(engineOpponent) {
7569                                SendToProgram("force\n", engineOpponent); // suppress reply
7570                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7571                              }
7572                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7573                              return 1;
7574                          }
7575                 }
7576
7577                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7578                     if(engineOpponent) {
7579                       SendToProgram("force\n", engineOpponent); // suppress reply
7580                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7581                     }
7582                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7583                     return 1;
7584                 }
7585         return 0;
7586 }
7587
7588 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7589 {   // [HGM] book: this routine intercepts moves to simulate book replies
7590     char *bookHit = NULL;
7591
7592     //first determine if the incoming move brings opponent into his book
7593     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7594         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7595     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7596     if(bookHit != NULL && !cps->bookSuspend) {
7597         // make sure opponent is not going to reply after receiving move to book position
7598         SendToProgram("force\n", cps);
7599         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7600     }
7601     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7602     // now arrange restart after book miss
7603     if(bookHit) {
7604         // after a book hit we never send 'go', and the code after the call to this routine
7605         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7606         char buf[MSG_SIZ], *move = bookHit;
7607         if(cps->useSAN) {
7608             int fromX, fromY, toX, toY;
7609             char promoChar;
7610             ChessMove moveType;
7611             move = buf + 30;
7612             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7613                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7614                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7615                                     PosFlags(forwardMostMove),
7616                                     fromY, fromX, toY, toX, promoChar, move);
7617             } else {
7618                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7619                 bookHit = NULL;
7620             }
7621         }
7622         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7623         SendToProgram(buf, cps);
7624         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7625     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7626         SendToProgram("go\n", cps);
7627         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7628     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7629         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7630             SendToProgram("go\n", cps);
7631         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7632     }
7633     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7634 }
7635
7636 char *savedMessage;
7637 ChessProgramState *savedState;
7638 void DeferredBookMove(void)
7639 {
7640         if(savedState->lastPing != savedState->lastPong)
7641                     ScheduleDelayedEvent(DeferredBookMove, 10);
7642         else
7643         HandleMachineMove(savedMessage, savedState);
7644 }
7645
7646 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7647
7648 void
7649 HandleMachineMove(message, cps)
7650      char *message;
7651      ChessProgramState *cps;
7652 {
7653     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7654     char realname[MSG_SIZ];
7655     int fromX, fromY, toX, toY;
7656     ChessMove moveType;
7657     char promoChar;
7658     char *p;
7659     int machineWhite;
7660     char *bookHit;
7661
7662     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7663         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7664         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
7665         pairingReceived = 1;
7666         NextMatchGame();
7667         return; // Skim the pairing messages here.
7668     }
7669
7670     cps->userError = 0;
7671
7672 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7673     /*
7674      * Kludge to ignore BEL characters
7675      */
7676     while (*message == '\007') message++;
7677
7678     /*
7679      * [HGM] engine debug message: ignore lines starting with '#' character
7680      */
7681     if(cps->debug && *message == '#') return;
7682
7683     /*
7684      * Look for book output
7685      */
7686     if (cps == &first && bookRequested) {
7687         if (message[0] == '\t' || message[0] == ' ') {
7688             /* Part of the book output is here; append it */
7689             strcat(bookOutput, message);
7690             strcat(bookOutput, "  \n");
7691             return;
7692         } else if (bookOutput[0] != NULLCHAR) {
7693             /* All of book output has arrived; display it */
7694             char *p = bookOutput;
7695             while (*p != NULLCHAR) {
7696                 if (*p == '\t') *p = ' ';
7697                 p++;
7698             }
7699             DisplayInformation(bookOutput);
7700             bookRequested = FALSE;
7701             /* Fall through to parse the current output */
7702         }
7703     }
7704
7705     /*
7706      * Look for machine move.
7707      */
7708     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7709         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7710     {
7711         /* This method is only useful on engines that support ping */
7712         if (cps->lastPing != cps->lastPong) {
7713           if (gameMode == BeginningOfGame) {
7714             /* Extra move from before last new; ignore */
7715             if (appData.debugMode) {
7716                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7717             }
7718           } else {
7719             if (appData.debugMode) {
7720                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7721                         cps->which, gameMode);
7722             }
7723
7724             SendToProgram("undo\n", cps);
7725           }
7726           return;
7727         }
7728
7729         switch (gameMode) {
7730           case BeginningOfGame:
7731             /* Extra move from before last reset; ignore */
7732             if (appData.debugMode) {
7733                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7734             }
7735             return;
7736
7737           case EndOfGame:
7738           case IcsIdle:
7739           default:
7740             /* Extra move after we tried to stop.  The mode test is
7741                not a reliable way of detecting this problem, but it's
7742                the best we can do on engines that don't support ping.
7743             */
7744             if (appData.debugMode) {
7745                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7746                         cps->which, gameMode);
7747             }
7748             SendToProgram("undo\n", cps);
7749             return;
7750
7751           case MachinePlaysWhite:
7752           case IcsPlayingWhite:
7753             machineWhite = TRUE;
7754             break;
7755
7756           case MachinePlaysBlack:
7757           case IcsPlayingBlack:
7758             machineWhite = FALSE;
7759             break;
7760
7761           case TwoMachinesPlay:
7762             machineWhite = (cps->twoMachinesColor[0] == 'w');
7763             break;
7764         }
7765         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7766             if (appData.debugMode) {
7767                 fprintf(debugFP,
7768                         "Ignoring move out of turn by %s, gameMode %d"
7769                         ", forwardMost %d\n",
7770                         cps->which, gameMode, forwardMostMove);
7771             }
7772             return;
7773         }
7774
7775     if (appData.debugMode) { int f = forwardMostMove;
7776         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7777                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7778                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7779     }
7780         if(cps->alphaRank) AlphaRank(machineMove, 4);
7781         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7782                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7783             /* Machine move could not be parsed; ignore it. */
7784           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7785                     machineMove, _(cps->which));
7786             DisplayError(buf1, 0);
7787             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7788                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7789             if (gameMode == TwoMachinesPlay) {
7790               GameEnds(machineWhite ? BlackWins : WhiteWins,
7791                        buf1, GE_XBOARD);
7792             }
7793             return;
7794         }
7795
7796         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7797         /* So we have to redo legality test with true e.p. status here,  */
7798         /* to make sure an illegal e.p. capture does not slip through,   */
7799         /* to cause a forfeit on a justified illegal-move complaint      */
7800         /* of the opponent.                                              */
7801         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7802            ChessMove moveType;
7803            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7804                              fromY, fromX, toY, toX, promoChar);
7805             if (appData.debugMode) {
7806                 int i;
7807                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7808                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7809                 fprintf(debugFP, "castling rights\n");
7810             }
7811             if(moveType == IllegalMove) {
7812               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7813                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7814                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7815                            buf1, GE_XBOARD);
7816                 return;
7817            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7818            /* [HGM] Kludge to handle engines that send FRC-style castling
7819               when they shouldn't (like TSCP-Gothic) */
7820            switch(moveType) {
7821              case WhiteASideCastleFR:
7822              case BlackASideCastleFR:
7823                toX+=2;
7824                currentMoveString[2]++;
7825                break;
7826              case WhiteHSideCastleFR:
7827              case BlackHSideCastleFR:
7828                toX--;
7829                currentMoveString[2]--;
7830                break;
7831              default: ; // nothing to do, but suppresses warning of pedantic compilers
7832            }
7833         }
7834         hintRequested = FALSE;
7835         lastHint[0] = NULLCHAR;
7836         bookRequested = FALSE;
7837         /* Program may be pondering now */
7838         cps->maybeThinking = TRUE;
7839         if (cps->sendTime == 2) cps->sendTime = 1;
7840         if (cps->offeredDraw) cps->offeredDraw--;
7841
7842         /* [AS] Save move info*/
7843         pvInfoList[ forwardMostMove ].score = programStats.score;
7844         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7845         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7846
7847         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7848
7849         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7850         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7851             int count = 0;
7852
7853             while( count < adjudicateLossPlies ) {
7854                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7855
7856                 if( count & 1 ) {
7857                     score = -score; /* Flip score for winning side */
7858                 }
7859
7860                 if( score > adjudicateLossThreshold ) {
7861                     break;
7862                 }
7863
7864                 count++;
7865             }
7866
7867             if( count >= adjudicateLossPlies ) {
7868                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7869
7870                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7871                     "Xboard adjudication",
7872                     GE_XBOARD );
7873
7874                 return;
7875             }
7876         }
7877
7878         if(Adjudicate(cps)) {
7879             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7880             return; // [HGM] adjudicate: for all automatic game ends
7881         }
7882
7883 #if ZIPPY
7884         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7885             first.initDone) {
7886           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7887                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7888                 SendToICS("draw ");
7889                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7890           }
7891           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7892           ics_user_moved = 1;
7893           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7894                 char buf[3*MSG_SIZ];
7895
7896                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7897                         programStats.score / 100.,
7898                         programStats.depth,
7899                         programStats.time / 100.,
7900                         (unsigned int)programStats.nodes,
7901                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7902                         programStats.movelist);
7903                 SendToICS(buf);
7904 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7905           }
7906         }
7907 #endif
7908
7909         /* [AS] Clear stats for next move */
7910         ClearProgramStats();
7911         thinkOutput[0] = NULLCHAR;
7912         hiddenThinkOutputState = 0;
7913
7914         bookHit = NULL;
7915         if (gameMode == TwoMachinesPlay) {
7916             /* [HGM] relaying draw offers moved to after reception of move */
7917             /* and interpreting offer as claim if it brings draw condition */
7918             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7919                 SendToProgram("draw\n", cps->other);
7920             }
7921             if (cps->other->sendTime) {
7922                 SendTimeRemaining(cps->other,
7923                                   cps->other->twoMachinesColor[0] == 'w');
7924             }
7925             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7926             if (firstMove && !bookHit) {
7927                 firstMove = FALSE;
7928                 if (cps->other->useColors) {
7929                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7930                 }
7931                 SendToProgram("go\n", cps->other);
7932             }
7933             cps->other->maybeThinking = TRUE;
7934         }
7935
7936         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7937
7938         if (!pausing && appData.ringBellAfterMoves) {
7939             RingBell();
7940         }
7941
7942         /*
7943          * Reenable menu items that were disabled while
7944          * machine was thinking
7945          */
7946         if (gameMode != TwoMachinesPlay)
7947             SetUserThinkingEnables();
7948
7949         // [HGM] book: after book hit opponent has received move and is now in force mode
7950         // force the book reply into it, and then fake that it outputted this move by jumping
7951         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7952         if(bookHit) {
7953                 static char bookMove[MSG_SIZ]; // a bit generous?
7954
7955                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7956                 strcat(bookMove, bookHit);
7957                 message = bookMove;
7958                 cps = cps->other;
7959                 programStats.nodes = programStats.depth = programStats.time =
7960                 programStats.score = programStats.got_only_move = 0;
7961                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7962
7963                 if(cps->lastPing != cps->lastPong) {
7964                     savedMessage = message; // args for deferred call
7965                     savedState = cps;
7966                     ScheduleDelayedEvent(DeferredBookMove, 10);
7967                     return;
7968                 }
7969                 goto FakeBookMove;
7970         }
7971
7972         return;
7973     }
7974
7975     /* Set special modes for chess engines.  Later something general
7976      *  could be added here; for now there is just one kludge feature,
7977      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7978      *  when "xboard" is given as an interactive command.
7979      */
7980     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7981         cps->useSigint = FALSE;
7982         cps->useSigterm = FALSE;
7983     }
7984     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7985       ParseFeatures(message+8, cps);
7986       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7987     }
7988
7989     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7990       int dummy, s=6; char buf[MSG_SIZ];
7991       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7992       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7993       ParseFEN(boards[0], &dummy, message+s);
7994       DrawPosition(TRUE, boards[0]);
7995       startedFromSetupPosition = TRUE;
7996       return;
7997     }
7998     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7999      * want this, I was asked to put it in, and obliged.
8000      */
8001     if (!strncmp(message, "setboard ", 9)) {
8002         Board initial_position;
8003
8004         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8005
8006         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8007             DisplayError(_("Bad FEN received from engine"), 0);
8008             return ;
8009         } else {
8010            Reset(TRUE, FALSE);
8011            CopyBoard(boards[0], initial_position);
8012            initialRulePlies = FENrulePlies;
8013            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8014            else gameMode = MachinePlaysBlack;
8015            DrawPosition(FALSE, boards[currentMove]);
8016         }
8017         return;
8018     }
8019
8020     /*
8021      * Look for communication commands
8022      */
8023     if (!strncmp(message, "telluser ", 9)) {
8024         if(message[9] == '\\' && message[10] == '\\')
8025             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8026         DisplayNote(message + 9);
8027         return;
8028     }
8029     if (!strncmp(message, "tellusererror ", 14)) {
8030         cps->userError = 1;
8031         if(message[14] == '\\' && message[15] == '\\')
8032             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8033         DisplayError(message + 14, 0);
8034         return;
8035     }
8036     if (!strncmp(message, "tellopponent ", 13)) {
8037       if (appData.icsActive) {
8038         if (loggedOn) {
8039           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8040           SendToICS(buf1);
8041         }
8042       } else {
8043         DisplayNote(message + 13);
8044       }
8045       return;
8046     }
8047     if (!strncmp(message, "tellothers ", 11)) {
8048       if (appData.icsActive) {
8049         if (loggedOn) {
8050           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8051           SendToICS(buf1);
8052         }
8053       }
8054       return;
8055     }
8056     if (!strncmp(message, "tellall ", 8)) {
8057       if (appData.icsActive) {
8058         if (loggedOn) {
8059           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8060           SendToICS(buf1);
8061         }
8062       } else {
8063         DisplayNote(message + 8);
8064       }
8065       return;
8066     }
8067     if (strncmp(message, "warning", 7) == 0) {
8068         /* Undocumented feature, use tellusererror in new code */
8069         DisplayError(message, 0);
8070         return;
8071     }
8072     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8073         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8074         strcat(realname, " query");
8075         AskQuestion(realname, buf2, buf1, cps->pr);
8076         return;
8077     }
8078     /* Commands from the engine directly to ICS.  We don't allow these to be
8079      *  sent until we are logged on. Crafty kibitzes have been known to
8080      *  interfere with the login process.
8081      */
8082     if (loggedOn) {
8083         if (!strncmp(message, "tellics ", 8)) {
8084             SendToICS(message + 8);
8085             SendToICS("\n");
8086             return;
8087         }
8088         if (!strncmp(message, "tellicsnoalias ", 15)) {
8089             SendToICS(ics_prefix);
8090             SendToICS(message + 15);
8091             SendToICS("\n");
8092             return;
8093         }
8094         /* The following are for backward compatibility only */
8095         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8096             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8097             SendToICS(ics_prefix);
8098             SendToICS(message);
8099             SendToICS("\n");
8100             return;
8101         }
8102     }
8103     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8104         return;
8105     }
8106     /*
8107      * If the move is illegal, cancel it and redraw the board.
8108      * Also deal with other error cases.  Matching is rather loose
8109      * here to accommodate engines written before the spec.
8110      */
8111     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8112         strncmp(message, "Error", 5) == 0) {
8113         if (StrStr(message, "name") ||
8114             StrStr(message, "rating") || StrStr(message, "?") ||
8115             StrStr(message, "result") || StrStr(message, "board") ||
8116             StrStr(message, "bk") || StrStr(message, "computer") ||
8117             StrStr(message, "variant") || StrStr(message, "hint") ||
8118             StrStr(message, "random") || StrStr(message, "depth") ||
8119             StrStr(message, "accepted")) {
8120             return;
8121         }
8122         if (StrStr(message, "protover")) {
8123           /* Program is responding to input, so it's apparently done
8124              initializing, and this error message indicates it is
8125              protocol version 1.  So we don't need to wait any longer
8126              for it to initialize and send feature commands. */
8127           FeatureDone(cps, 1);
8128           cps->protocolVersion = 1;
8129           return;
8130         }
8131         cps->maybeThinking = FALSE;
8132
8133         if (StrStr(message, "draw")) {
8134             /* Program doesn't have "draw" command */
8135             cps->sendDrawOffers = 0;
8136             return;
8137         }
8138         if (cps->sendTime != 1 &&
8139             (StrStr(message, "time") || StrStr(message, "otim"))) {
8140           /* Program apparently doesn't have "time" or "otim" command */
8141           cps->sendTime = 0;
8142           return;
8143         }
8144         if (StrStr(message, "analyze")) {
8145             cps->analysisSupport = FALSE;
8146             cps->analyzing = FALSE;
8147             Reset(FALSE, TRUE);
8148             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8149             DisplayError(buf2, 0);
8150             return;
8151         }
8152         if (StrStr(message, "(no matching move)st")) {
8153           /* Special kludge for GNU Chess 4 only */
8154           cps->stKludge = TRUE;
8155           SendTimeControl(cps, movesPerSession, timeControl,
8156                           timeIncrement, appData.searchDepth,
8157                           searchTime);
8158           return;
8159         }
8160         if (StrStr(message, "(no matching move)sd")) {
8161           /* Special kludge for GNU Chess 4 only */
8162           cps->sdKludge = TRUE;
8163           SendTimeControl(cps, movesPerSession, timeControl,
8164                           timeIncrement, appData.searchDepth,
8165                           searchTime);
8166           return;
8167         }
8168         if (!StrStr(message, "llegal")) {
8169             return;
8170         }
8171         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8172             gameMode == IcsIdle) return;
8173         if (forwardMostMove <= backwardMostMove) return;
8174         if (pausing) PauseEvent();
8175       if(appData.forceIllegal) {
8176             // [HGM] illegal: machine refused move; force position after move into it
8177           SendToProgram("force\n", cps);
8178           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8179                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8180                 // when black is to move, while there might be nothing on a2 or black
8181                 // might already have the move. So send the board as if white has the move.
8182                 // But first we must change the stm of the engine, as it refused the last move
8183                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8184                 if(WhiteOnMove(forwardMostMove)) {
8185                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8186                     SendBoard(cps, forwardMostMove); // kludgeless board
8187                 } else {
8188                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8189                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8190                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8191                 }
8192           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8193             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8194                  gameMode == TwoMachinesPlay)
8195               SendToProgram("go\n", cps);
8196             return;
8197       } else
8198         if (gameMode == PlayFromGameFile) {
8199             /* Stop reading this game file */
8200             gameMode = EditGame;
8201             ModeHighlight();
8202         }
8203         /* [HGM] illegal-move claim should forfeit game when Xboard */
8204         /* only passes fully legal moves                            */
8205         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8206             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8207                                 "False illegal-move claim", GE_XBOARD );
8208             return; // do not take back move we tested as valid
8209         }
8210         currentMove = forwardMostMove-1;
8211         DisplayMove(currentMove-1); /* before DisplayMoveError */
8212         SwitchClocks(forwardMostMove-1); // [HGM] race
8213         DisplayBothClocks();
8214         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8215                 parseList[currentMove], _(cps->which));
8216         DisplayMoveError(buf1);
8217         DrawPosition(FALSE, boards[currentMove]);
8218         return;
8219     }
8220     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8221         /* Program has a broken "time" command that
8222            outputs a string not ending in newline.
8223            Don't use it. */
8224         cps->sendTime = 0;
8225     }
8226
8227     /*
8228      * If chess program startup fails, exit with an error message.
8229      * Attempts to recover here are futile.
8230      */
8231     if ((StrStr(message, "unknown host") != NULL)
8232         || (StrStr(message, "No remote directory") != NULL)
8233         || (StrStr(message, "not found") != NULL)
8234         || (StrStr(message, "No such file") != NULL)
8235         || (StrStr(message, "can't alloc") != NULL)
8236         || (StrStr(message, "Permission denied") != NULL)) {
8237
8238         cps->maybeThinking = FALSE;
8239         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8240                 _(cps->which), cps->program, cps->host, message);
8241         RemoveInputSource(cps->isr);
8242         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8243             if(cps == &first) appData.noChessProgram = TRUE;
8244             DisplayError(buf1, 0);
8245         }
8246         return;
8247     }
8248
8249     /*
8250      * Look for hint output
8251      */
8252     if (sscanf(message, "Hint: %s", buf1) == 1) {
8253         if (cps == &first && hintRequested) {
8254             hintRequested = FALSE;
8255             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8256                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8257                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8258                                     PosFlags(forwardMostMove),
8259                                     fromY, fromX, toY, toX, promoChar, buf1);
8260                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8261                 DisplayInformation(buf2);
8262             } else {
8263                 /* Hint move could not be parsed!? */
8264               snprintf(buf2, sizeof(buf2),
8265                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8266                         buf1, _(cps->which));
8267                 DisplayError(buf2, 0);
8268             }
8269         } else {
8270           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8271         }
8272         return;
8273     }
8274
8275     /*
8276      * Ignore other messages if game is not in progress
8277      */
8278     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8279         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8280
8281     /*
8282      * look for win, lose, draw, or draw offer
8283      */
8284     if (strncmp(message, "1-0", 3) == 0) {
8285         char *p, *q, *r = "";
8286         p = strchr(message, '{');
8287         if (p) {
8288             q = strchr(p, '}');
8289             if (q) {
8290                 *q = NULLCHAR;
8291                 r = p + 1;
8292             }
8293         }
8294         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8295         return;
8296     } else if (strncmp(message, "0-1", 3) == 0) {
8297         char *p, *q, *r = "";
8298         p = strchr(message, '{');
8299         if (p) {
8300             q = strchr(p, '}');
8301             if (q) {
8302                 *q = NULLCHAR;
8303                 r = p + 1;
8304             }
8305         }
8306         /* Kludge for Arasan 4.1 bug */
8307         if (strcmp(r, "Black resigns") == 0) {
8308             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8309             return;
8310         }
8311         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8312         return;
8313     } else if (strncmp(message, "1/2", 3) == 0) {
8314         char *p, *q, *r = "";
8315         p = strchr(message, '{');
8316         if (p) {
8317             q = strchr(p, '}');
8318             if (q) {
8319                 *q = NULLCHAR;
8320                 r = p + 1;
8321             }
8322         }
8323
8324         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8325         return;
8326
8327     } else if (strncmp(message, "White resign", 12) == 0) {
8328         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8329         return;
8330     } else if (strncmp(message, "Black resign", 12) == 0) {
8331         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8332         return;
8333     } else if (strncmp(message, "White matches", 13) == 0 ||
8334                strncmp(message, "Black matches", 13) == 0   ) {
8335         /* [HGM] ignore GNUShogi noises */
8336         return;
8337     } else if (strncmp(message, "White", 5) == 0 &&
8338                message[5] != '(' &&
8339                StrStr(message, "Black") == NULL) {
8340         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8341         return;
8342     } else if (strncmp(message, "Black", 5) == 0 &&
8343                message[5] != '(') {
8344         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8345         return;
8346     } else if (strcmp(message, "resign") == 0 ||
8347                strcmp(message, "computer resigns") == 0) {
8348         switch (gameMode) {
8349           case MachinePlaysBlack:
8350           case IcsPlayingBlack:
8351             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8352             break;
8353           case MachinePlaysWhite:
8354           case IcsPlayingWhite:
8355             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8356             break;
8357           case TwoMachinesPlay:
8358             if (cps->twoMachinesColor[0] == 'w')
8359               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8360             else
8361               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8362             break;
8363           default:
8364             /* can't happen */
8365             break;
8366         }
8367         return;
8368     } else if (strncmp(message, "opponent mates", 14) == 0) {
8369         switch (gameMode) {
8370           case MachinePlaysBlack:
8371           case IcsPlayingBlack:
8372             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8373             break;
8374           case MachinePlaysWhite:
8375           case IcsPlayingWhite:
8376             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8377             break;
8378           case TwoMachinesPlay:
8379             if (cps->twoMachinesColor[0] == 'w')
8380               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8381             else
8382               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8383             break;
8384           default:
8385             /* can't happen */
8386             break;
8387         }
8388         return;
8389     } else if (strncmp(message, "computer mates", 14) == 0) {
8390         switch (gameMode) {
8391           case MachinePlaysBlack:
8392           case IcsPlayingBlack:
8393             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8394             break;
8395           case MachinePlaysWhite:
8396           case IcsPlayingWhite:
8397             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8398             break;
8399           case TwoMachinesPlay:
8400             if (cps->twoMachinesColor[0] == 'w')
8401               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8402             else
8403               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8404             break;
8405           default:
8406             /* can't happen */
8407             break;
8408         }
8409         return;
8410     } else if (strncmp(message, "checkmate", 9) == 0) {
8411         if (WhiteOnMove(forwardMostMove)) {
8412             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8413         } else {
8414             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8415         }
8416         return;
8417     } else if (strstr(message, "Draw") != NULL ||
8418                strstr(message, "game is a draw") != NULL) {
8419         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8420         return;
8421     } else if (strstr(message, "offer") != NULL &&
8422                strstr(message, "draw") != NULL) {
8423 #if ZIPPY
8424         if (appData.zippyPlay && first.initDone) {
8425             /* Relay offer to ICS */
8426             SendToICS(ics_prefix);
8427             SendToICS("draw\n");
8428         }
8429 #endif
8430         cps->offeredDraw = 2; /* valid until this engine moves twice */
8431         if (gameMode == TwoMachinesPlay) {
8432             if (cps->other->offeredDraw) {
8433                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8434             /* [HGM] in two-machine mode we delay relaying draw offer      */
8435             /* until after we also have move, to see if it is really claim */
8436             }
8437         } else if (gameMode == MachinePlaysWhite ||
8438                    gameMode == MachinePlaysBlack) {
8439           if (userOfferedDraw) {
8440             DisplayInformation(_("Machine accepts your draw offer"));
8441             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8442           } else {
8443             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8444           }
8445         }
8446     }
8447
8448
8449     /*
8450      * Look for thinking output
8451      */
8452     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8453           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8454                                 ) {
8455         int plylev, mvleft, mvtot, curscore, time;
8456         char mvname[MOVE_LEN];
8457         u64 nodes; // [DM]
8458         char plyext;
8459         int ignore = FALSE;
8460         int prefixHint = FALSE;
8461         mvname[0] = NULLCHAR;
8462
8463         switch (gameMode) {
8464           case MachinePlaysBlack:
8465           case IcsPlayingBlack:
8466             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8467             break;
8468           case MachinePlaysWhite:
8469           case IcsPlayingWhite:
8470             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8471             break;
8472           case AnalyzeMode:
8473           case AnalyzeFile:
8474             break;
8475           case IcsObserving: /* [DM] icsEngineAnalyze */
8476             if (!appData.icsEngineAnalyze) ignore = TRUE;
8477             break;
8478           case TwoMachinesPlay:
8479             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8480                 ignore = TRUE;
8481             }
8482             break;
8483           default:
8484             ignore = TRUE;
8485             break;
8486         }
8487
8488         if (!ignore) {
8489             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8490             buf1[0] = NULLCHAR;
8491             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8492                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8493
8494                 if (plyext != ' ' && plyext != '\t') {
8495                     time *= 100;
8496                 }
8497
8498                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8499                 if( cps->scoreIsAbsolute &&
8500                     ( gameMode == MachinePlaysBlack ||
8501                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8502                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8503                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8504                      !WhiteOnMove(currentMove)
8505                     ) )
8506                 {
8507                     curscore = -curscore;
8508                 }
8509
8510
8511                 tempStats.depth = plylev;
8512                 tempStats.nodes = nodes;
8513                 tempStats.time = time;
8514                 tempStats.score = curscore;
8515                 tempStats.got_only_move = 0;
8516
8517                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8518                         int ticklen;
8519
8520                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8521                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8522                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8523                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8524                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8525                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8526                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8527                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8528                 }
8529
8530                 /* Buffer overflow protection */
8531                 if (buf1[0] != NULLCHAR) {
8532                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8533                         && appData.debugMode) {
8534                         fprintf(debugFP,
8535                                 "PV is too long; using the first %u bytes.\n",
8536                                 (unsigned) sizeof(tempStats.movelist) - 1);
8537                     }
8538
8539                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8540                 } else {
8541                     sprintf(tempStats.movelist, " no PV\n");
8542                 }
8543
8544                 if (tempStats.seen_stat) {
8545                     tempStats.ok_to_send = 1;
8546                 }
8547
8548                 if (strchr(tempStats.movelist, '(') != NULL) {
8549                     tempStats.line_is_book = 1;
8550                     tempStats.nr_moves = 0;
8551                     tempStats.moves_left = 0;
8552                 } else {
8553                     tempStats.line_is_book = 0;
8554                 }
8555
8556                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8557                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8558
8559                 SendProgramStatsToFrontend( cps, &tempStats );
8560
8561                 /*
8562                     [AS] Protect the thinkOutput buffer from overflow... this
8563                     is only useful if buf1 hasn't overflowed first!
8564                 */
8565                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8566                          plylev,
8567                          (gameMode == TwoMachinesPlay ?
8568                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8569                          ((double) curscore) / 100.0,
8570                          prefixHint ? lastHint : "",
8571                          prefixHint ? " " : "" );
8572
8573                 if( buf1[0] != NULLCHAR ) {
8574                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8575
8576                     if( strlen(buf1) > max_len ) {
8577                         if( appData.debugMode) {
8578                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8579                         }
8580                         buf1[max_len+1] = '\0';
8581                     }
8582
8583                     strcat( thinkOutput, buf1 );
8584                 }
8585
8586                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8587                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8588                     DisplayMove(currentMove - 1);
8589                 }
8590                 return;
8591
8592             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8593                 /* crafty (9.25+) says "(only move) <move>"
8594                  * if there is only 1 legal move
8595                  */
8596                 sscanf(p, "(only move) %s", buf1);
8597                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8598                 sprintf(programStats.movelist, "%s (only move)", buf1);
8599                 programStats.depth = 1;
8600                 programStats.nr_moves = 1;
8601                 programStats.moves_left = 1;
8602                 programStats.nodes = 1;
8603                 programStats.time = 1;
8604                 programStats.got_only_move = 1;
8605
8606                 /* Not really, but we also use this member to
8607                    mean "line isn't going to change" (Crafty
8608                    isn't searching, so stats won't change) */
8609                 programStats.line_is_book = 1;
8610
8611                 SendProgramStatsToFrontend( cps, &programStats );
8612
8613                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8614                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8615                     DisplayMove(currentMove - 1);
8616                 }
8617                 return;
8618             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8619                               &time, &nodes, &plylev, &mvleft,
8620                               &mvtot, mvname) >= 5) {
8621                 /* The stat01: line is from Crafty (9.29+) in response
8622                    to the "." command */
8623                 programStats.seen_stat = 1;
8624                 cps->maybeThinking = TRUE;
8625
8626                 if (programStats.got_only_move || !appData.periodicUpdates)
8627                   return;
8628
8629                 programStats.depth = plylev;
8630                 programStats.time = time;
8631                 programStats.nodes = nodes;
8632                 programStats.moves_left = mvleft;
8633                 programStats.nr_moves = mvtot;
8634                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8635                 programStats.ok_to_send = 1;
8636                 programStats.movelist[0] = '\0';
8637
8638                 SendProgramStatsToFrontend( cps, &programStats );
8639
8640                 return;
8641
8642             } else if (strncmp(message,"++",2) == 0) {
8643                 /* Crafty 9.29+ outputs this */
8644                 programStats.got_fail = 2;
8645                 return;
8646
8647             } else if (strncmp(message,"--",2) == 0) {
8648                 /* Crafty 9.29+ outputs this */
8649                 programStats.got_fail = 1;
8650                 return;
8651
8652             } else if (thinkOutput[0] != NULLCHAR &&
8653                        strncmp(message, "    ", 4) == 0) {
8654                 unsigned message_len;
8655
8656                 p = message;
8657                 while (*p && *p == ' ') p++;
8658
8659                 message_len = strlen( p );
8660
8661                 /* [AS] Avoid buffer overflow */
8662                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8663                     strcat(thinkOutput, " ");
8664                     strcat(thinkOutput, p);
8665                 }
8666
8667                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8668                     strcat(programStats.movelist, " ");
8669                     strcat(programStats.movelist, p);
8670                 }
8671
8672                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8673                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8674                     DisplayMove(currentMove - 1);
8675                 }
8676                 return;
8677             }
8678         }
8679         else {
8680             buf1[0] = NULLCHAR;
8681
8682             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8683                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8684             {
8685                 ChessProgramStats cpstats;
8686
8687                 if (plyext != ' ' && plyext != '\t') {
8688                     time *= 100;
8689                 }
8690
8691                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8692                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8693                     curscore = -curscore;
8694                 }
8695
8696                 cpstats.depth = plylev;
8697                 cpstats.nodes = nodes;
8698                 cpstats.time = time;
8699                 cpstats.score = curscore;
8700                 cpstats.got_only_move = 0;
8701                 cpstats.movelist[0] = '\0';
8702
8703                 if (buf1[0] != NULLCHAR) {
8704                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8705                 }
8706
8707                 cpstats.ok_to_send = 0;
8708                 cpstats.line_is_book = 0;
8709                 cpstats.nr_moves = 0;
8710                 cpstats.moves_left = 0;
8711
8712                 SendProgramStatsToFrontend( cps, &cpstats );
8713             }
8714         }
8715     }
8716 }
8717
8718
8719 /* Parse a game score from the character string "game", and
8720    record it as the history of the current game.  The game
8721    score is NOT assumed to start from the standard position.
8722    The display is not updated in any way.
8723    */
8724 void
8725 ParseGameHistory(game)
8726      char *game;
8727 {
8728     ChessMove moveType;
8729     int fromX, fromY, toX, toY, boardIndex;
8730     char promoChar;
8731     char *p, *q;
8732     char buf[MSG_SIZ];
8733
8734     if (appData.debugMode)
8735       fprintf(debugFP, "Parsing game history: %s\n", game);
8736
8737     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8738     gameInfo.site = StrSave(appData.icsHost);
8739     gameInfo.date = PGNDate();
8740     gameInfo.round = StrSave("-");
8741
8742     /* Parse out names of players */
8743     while (*game == ' ') game++;
8744     p = buf;
8745     while (*game != ' ') *p++ = *game++;
8746     *p = NULLCHAR;
8747     gameInfo.white = StrSave(buf);
8748     while (*game == ' ') game++;
8749     p = buf;
8750     while (*game != ' ' && *game != '\n') *p++ = *game++;
8751     *p = NULLCHAR;
8752     gameInfo.black = StrSave(buf);
8753
8754     /* Parse moves */
8755     boardIndex = blackPlaysFirst ? 1 : 0;
8756     yynewstr(game);
8757     for (;;) {
8758         yyboardindex = boardIndex;
8759         moveType = (ChessMove) Myylex();
8760         switch (moveType) {
8761           case IllegalMove:             /* maybe suicide chess, etc. */
8762   if (appData.debugMode) {
8763     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8764     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8765     setbuf(debugFP, NULL);
8766   }
8767           case WhitePromotion:
8768           case BlackPromotion:
8769           case WhiteNonPromotion:
8770           case BlackNonPromotion:
8771           case NormalMove:
8772           case WhiteCapturesEnPassant:
8773           case BlackCapturesEnPassant:
8774           case WhiteKingSideCastle:
8775           case WhiteQueenSideCastle:
8776           case BlackKingSideCastle:
8777           case BlackQueenSideCastle:
8778           case WhiteKingSideCastleWild:
8779           case WhiteQueenSideCastleWild:
8780           case BlackKingSideCastleWild:
8781           case BlackQueenSideCastleWild:
8782           /* PUSH Fabien */
8783           case WhiteHSideCastleFR:
8784           case WhiteASideCastleFR:
8785           case BlackHSideCastleFR:
8786           case BlackASideCastleFR:
8787           /* POP Fabien */
8788             fromX = currentMoveString[0] - AAA;
8789             fromY = currentMoveString[1] - ONE;
8790             toX = currentMoveString[2] - AAA;
8791             toY = currentMoveString[3] - ONE;
8792             promoChar = currentMoveString[4];
8793             break;
8794           case WhiteDrop:
8795           case BlackDrop:
8796             fromX = moveType == WhiteDrop ?
8797               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8798             (int) CharToPiece(ToLower(currentMoveString[0]));
8799             fromY = DROP_RANK;
8800             toX = currentMoveString[2] - AAA;
8801             toY = currentMoveString[3] - ONE;
8802             promoChar = NULLCHAR;
8803             break;
8804           case AmbiguousMove:
8805             /* bug? */
8806             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8807   if (appData.debugMode) {
8808     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8809     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8810     setbuf(debugFP, NULL);
8811   }
8812             DisplayError(buf, 0);
8813             return;
8814           case ImpossibleMove:
8815             /* bug? */
8816             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8817   if (appData.debugMode) {
8818     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8819     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8820     setbuf(debugFP, NULL);
8821   }
8822             DisplayError(buf, 0);
8823             return;
8824           case EndOfFile:
8825             if (boardIndex < backwardMostMove) {
8826                 /* Oops, gap.  How did that happen? */
8827                 DisplayError(_("Gap in move list"), 0);
8828                 return;
8829             }
8830             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8831             if (boardIndex > forwardMostMove) {
8832                 forwardMostMove = boardIndex;
8833             }
8834             return;
8835           case ElapsedTime:
8836             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8837                 strcat(parseList[boardIndex-1], " ");
8838                 strcat(parseList[boardIndex-1], yy_text);
8839             }
8840             continue;
8841           case Comment:
8842           case PGNTag:
8843           case NAG:
8844           default:
8845             /* ignore */
8846             continue;
8847           case WhiteWins:
8848           case BlackWins:
8849           case GameIsDrawn:
8850           case GameUnfinished:
8851             if (gameMode == IcsExamining) {
8852                 if (boardIndex < backwardMostMove) {
8853                     /* Oops, gap.  How did that happen? */
8854                     return;
8855                 }
8856                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8857                 return;
8858             }
8859             gameInfo.result = moveType;
8860             p = strchr(yy_text, '{');
8861             if (p == NULL) p = strchr(yy_text, '(');
8862             if (p == NULL) {
8863                 p = yy_text;
8864                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8865             } else {
8866                 q = strchr(p, *p == '{' ? '}' : ')');
8867                 if (q != NULL) *q = NULLCHAR;
8868                 p++;
8869             }
8870             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8871             gameInfo.resultDetails = StrSave(p);
8872             continue;
8873         }
8874         if (boardIndex >= forwardMostMove &&
8875             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8876             backwardMostMove = blackPlaysFirst ? 1 : 0;
8877             return;
8878         }
8879         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8880                                  fromY, fromX, toY, toX, promoChar,
8881                                  parseList[boardIndex]);
8882         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8883         /* currentMoveString is set as a side-effect of yylex */
8884         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8885         strcat(moveList[boardIndex], "\n");
8886         boardIndex++;
8887         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8888         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8889           case MT_NONE:
8890           case MT_STALEMATE:
8891           default:
8892             break;
8893           case MT_CHECK:
8894             if(gameInfo.variant != VariantShogi)
8895                 strcat(parseList[boardIndex - 1], "+");
8896             break;
8897           case MT_CHECKMATE:
8898           case MT_STAINMATE:
8899             strcat(parseList[boardIndex - 1], "#");
8900             break;
8901         }
8902     }
8903 }
8904
8905
8906 /* Apply a move to the given board  */
8907 void
8908 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8909      int fromX, fromY, toX, toY;
8910      int promoChar;
8911      Board board;
8912 {
8913   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8914   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8915
8916     /* [HGM] compute & store e.p. status and castling rights for new position */
8917     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8918
8919       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8920       oldEP = (signed char)board[EP_STATUS];
8921       board[EP_STATUS] = EP_NONE;
8922
8923       if( board[toY][toX] != EmptySquare )
8924            board[EP_STATUS] = EP_CAPTURE;
8925
8926   if (fromY == DROP_RANK) {
8927         /* must be first */
8928         piece = board[toY][toX] = (ChessSquare) fromX;
8929   } else {
8930       int i;
8931
8932       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8933            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8934                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8935       } else
8936       if( board[fromY][fromX] == WhitePawn ) {
8937            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8938                board[EP_STATUS] = EP_PAWN_MOVE;
8939            if( toY-fromY==2) {
8940                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8941                         gameInfo.variant != VariantBerolina || toX < fromX)
8942                       board[EP_STATUS] = toX | berolina;
8943                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8944                         gameInfo.variant != VariantBerolina || toX > fromX)
8945                       board[EP_STATUS] = toX;
8946            }
8947       } else
8948       if( board[fromY][fromX] == BlackPawn ) {
8949            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8950                board[EP_STATUS] = EP_PAWN_MOVE;
8951            if( toY-fromY== -2) {
8952                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8953                         gameInfo.variant != VariantBerolina || toX < fromX)
8954                       board[EP_STATUS] = toX | berolina;
8955                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8956                         gameInfo.variant != VariantBerolina || toX > fromX)
8957                       board[EP_STATUS] = toX;
8958            }
8959        }
8960
8961        for(i=0; i<nrCastlingRights; i++) {
8962            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8963               board[CASTLING][i] == toX   && castlingRank[i] == toY
8964              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8965        }
8966
8967      if (fromX == toX && fromY == toY) return;
8968
8969      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8970      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8971      if(gameInfo.variant == VariantKnightmate)
8972          king += (int) WhiteUnicorn - (int) WhiteKing;
8973
8974     /* Code added by Tord: */
8975     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8976     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8977         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8978       board[fromY][fromX] = EmptySquare;
8979       board[toY][toX] = EmptySquare;
8980       if((toX > fromX) != (piece == WhiteRook)) {
8981         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8982       } else {
8983         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8984       }
8985     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8986                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8987       board[fromY][fromX] = EmptySquare;
8988       board[toY][toX] = EmptySquare;
8989       if((toX > fromX) != (piece == BlackRook)) {
8990         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8991       } else {
8992         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8993       }
8994     /* End of code added by Tord */
8995
8996     } else if (board[fromY][fromX] == king
8997         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8998         && toY == fromY && toX > fromX+1) {
8999         board[fromY][fromX] = EmptySquare;
9000         board[toY][toX] = king;
9001         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9002         board[fromY][BOARD_RGHT-1] = EmptySquare;
9003     } else if (board[fromY][fromX] == king
9004         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9005                && toY == fromY && toX < fromX-1) {
9006         board[fromY][fromX] = EmptySquare;
9007         board[toY][toX] = king;
9008         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9009         board[fromY][BOARD_LEFT] = EmptySquare;
9010     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9011                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9012                && toY >= BOARD_HEIGHT-promoRank
9013                ) {
9014         /* white pawn promotion */
9015         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9016         if (board[toY][toX] == EmptySquare) {
9017             board[toY][toX] = WhiteQueen;
9018         }
9019         if(gameInfo.variant==VariantBughouse ||
9020            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9021             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9022         board[fromY][fromX] = EmptySquare;
9023     } else if ((fromY == BOARD_HEIGHT-4)
9024                && (toX != fromX)
9025                && gameInfo.variant != VariantXiangqi
9026                && gameInfo.variant != VariantBerolina
9027                && (board[fromY][fromX] == WhitePawn)
9028                && (board[toY][toX] == EmptySquare)) {
9029         board[fromY][fromX] = EmptySquare;
9030         board[toY][toX] = WhitePawn;
9031         captured = board[toY - 1][toX];
9032         board[toY - 1][toX] = EmptySquare;
9033     } else if ((fromY == BOARD_HEIGHT-4)
9034                && (toX == fromX)
9035                && gameInfo.variant == VariantBerolina
9036                && (board[fromY][fromX] == WhitePawn)
9037                && (board[toY][toX] == EmptySquare)) {
9038         board[fromY][fromX] = EmptySquare;
9039         board[toY][toX] = WhitePawn;
9040         if(oldEP & EP_BEROLIN_A) {
9041                 captured = board[fromY][fromX-1];
9042                 board[fromY][fromX-1] = EmptySquare;
9043         }else{  captured = board[fromY][fromX+1];
9044                 board[fromY][fromX+1] = EmptySquare;
9045         }
9046     } else if (board[fromY][fromX] == king
9047         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9048                && toY == fromY && toX > fromX+1) {
9049         board[fromY][fromX] = EmptySquare;
9050         board[toY][toX] = king;
9051         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9052         board[fromY][BOARD_RGHT-1] = EmptySquare;
9053     } else if (board[fromY][fromX] == king
9054         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9055                && toY == fromY && toX < fromX-1) {
9056         board[fromY][fromX] = EmptySquare;
9057         board[toY][toX] = king;
9058         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9059         board[fromY][BOARD_LEFT] = EmptySquare;
9060     } else if (fromY == 7 && fromX == 3
9061                && board[fromY][fromX] == BlackKing
9062                && toY == 7 && toX == 5) {
9063         board[fromY][fromX] = EmptySquare;
9064         board[toY][toX] = BlackKing;
9065         board[fromY][7] = EmptySquare;
9066         board[toY][4] = BlackRook;
9067     } else if (fromY == 7 && fromX == 3
9068                && board[fromY][fromX] == BlackKing
9069                && toY == 7 && toX == 1) {
9070         board[fromY][fromX] = EmptySquare;
9071         board[toY][toX] = BlackKing;
9072         board[fromY][0] = EmptySquare;
9073         board[toY][2] = BlackRook;
9074     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9075                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9076                && toY < promoRank
9077                ) {
9078         /* black pawn promotion */
9079         board[toY][toX] = CharToPiece(ToLower(promoChar));
9080         if (board[toY][toX] == EmptySquare) {
9081             board[toY][toX] = BlackQueen;
9082         }
9083         if(gameInfo.variant==VariantBughouse ||
9084            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9085             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9086         board[fromY][fromX] = EmptySquare;
9087     } else if ((fromY == 3)
9088                && (toX != fromX)
9089                && gameInfo.variant != VariantXiangqi
9090                && gameInfo.variant != VariantBerolina
9091                && (board[fromY][fromX] == BlackPawn)
9092                && (board[toY][toX] == EmptySquare)) {
9093         board[fromY][fromX] = EmptySquare;
9094         board[toY][toX] = BlackPawn;
9095         captured = board[toY + 1][toX];
9096         board[toY + 1][toX] = EmptySquare;
9097     } else if ((fromY == 3)
9098                && (toX == fromX)
9099                && gameInfo.variant == VariantBerolina
9100                && (board[fromY][fromX] == BlackPawn)
9101                && (board[toY][toX] == EmptySquare)) {
9102         board[fromY][fromX] = EmptySquare;
9103         board[toY][toX] = BlackPawn;
9104         if(oldEP & EP_BEROLIN_A) {
9105                 captured = board[fromY][fromX-1];
9106                 board[fromY][fromX-1] = EmptySquare;
9107         }else{  captured = board[fromY][fromX+1];
9108                 board[fromY][fromX+1] = EmptySquare;
9109         }
9110     } else {
9111         board[toY][toX] = board[fromY][fromX];
9112         board[fromY][fromX] = EmptySquare;
9113     }
9114   }
9115
9116     if (gameInfo.holdingsWidth != 0) {
9117
9118       /* !!A lot more code needs to be written to support holdings  */
9119       /* [HGM] OK, so I have written it. Holdings are stored in the */
9120       /* penultimate board files, so they are automaticlly stored   */
9121       /* in the game history.                                       */
9122       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9123                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9124         /* Delete from holdings, by decreasing count */
9125         /* and erasing image if necessary            */
9126         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9127         if(p < (int) BlackPawn) { /* white drop */
9128              p -= (int)WhitePawn;
9129                  p = PieceToNumber((ChessSquare)p);
9130              if(p >= gameInfo.holdingsSize) p = 0;
9131              if(--board[p][BOARD_WIDTH-2] <= 0)
9132                   board[p][BOARD_WIDTH-1] = EmptySquare;
9133              if((int)board[p][BOARD_WIDTH-2] < 0)
9134                         board[p][BOARD_WIDTH-2] = 0;
9135         } else {                  /* black drop */
9136              p -= (int)BlackPawn;
9137                  p = PieceToNumber((ChessSquare)p);
9138              if(p >= gameInfo.holdingsSize) p = 0;
9139              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9140                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9141              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9142                         board[BOARD_HEIGHT-1-p][1] = 0;
9143         }
9144       }
9145       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9146           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9147         /* [HGM] holdings: Add to holdings, if holdings exist */
9148         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9149                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9150                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9151         }
9152         p = (int) captured;
9153         if (p >= (int) BlackPawn) {
9154           p -= (int)BlackPawn;
9155           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9156                   /* in Shogi restore piece to its original  first */
9157                   captured = (ChessSquare) (DEMOTED captured);
9158                   p = DEMOTED p;
9159           }
9160           p = PieceToNumber((ChessSquare)p);
9161           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9162           board[p][BOARD_WIDTH-2]++;
9163           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9164         } else {
9165           p -= (int)WhitePawn;
9166           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9167                   captured = (ChessSquare) (DEMOTED captured);
9168                   p = DEMOTED p;
9169           }
9170           p = PieceToNumber((ChessSquare)p);
9171           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9172           board[BOARD_HEIGHT-1-p][1]++;
9173           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9174         }
9175       }
9176     } else if (gameInfo.variant == VariantAtomic) {
9177       if (captured != EmptySquare) {
9178         int y, x;
9179         for (y = toY-1; y <= toY+1; y++) {
9180           for (x = toX-1; x <= toX+1; x++) {
9181             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9182                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9183               board[y][x] = EmptySquare;
9184             }
9185           }
9186         }
9187         board[toY][toX] = EmptySquare;
9188       }
9189     }
9190     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9191         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9192     } else
9193     if(promoChar == '+') {
9194         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9195         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9196     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9197         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9198     }
9199     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9200                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9201         // [HGM] superchess: take promotion piece out of holdings
9202         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9203         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9204             if(!--board[k][BOARD_WIDTH-2])
9205                 board[k][BOARD_WIDTH-1] = EmptySquare;
9206         } else {
9207             if(!--board[BOARD_HEIGHT-1-k][1])
9208                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9209         }
9210     }
9211
9212 }
9213
9214 /* Updates forwardMostMove */
9215 void
9216 MakeMove(fromX, fromY, toX, toY, promoChar)
9217      int fromX, fromY, toX, toY;
9218      int promoChar;
9219 {
9220 //    forwardMostMove++; // [HGM] bare: moved downstream
9221
9222     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9223         int timeLeft; static int lastLoadFlag=0; int king, piece;
9224         piece = boards[forwardMostMove][fromY][fromX];
9225         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9226         if(gameInfo.variant == VariantKnightmate)
9227             king += (int) WhiteUnicorn - (int) WhiteKing;
9228         if(forwardMostMove == 0) {
9229             if(blackPlaysFirst)
9230                 fprintf(serverMoves, "%s;", second.tidy);
9231             fprintf(serverMoves, "%s;", first.tidy);
9232             if(!blackPlaysFirst)
9233                 fprintf(serverMoves, "%s;", second.tidy);
9234         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9235         lastLoadFlag = loadFlag;
9236         // print base move
9237         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9238         // print castling suffix
9239         if( toY == fromY && piece == king ) {
9240             if(toX-fromX > 1)
9241                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9242             if(fromX-toX >1)
9243                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9244         }
9245         // e.p. suffix
9246         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9247              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9248              boards[forwardMostMove][toY][toX] == EmptySquare
9249              && fromX != toX && fromY != toY)
9250                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9251         // promotion suffix
9252         if(promoChar != NULLCHAR)
9253                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9254         if(!loadFlag) {
9255             fprintf(serverMoves, "/%d/%d",
9256                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9257             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9258             else                      timeLeft = blackTimeRemaining/1000;
9259             fprintf(serverMoves, "/%d", timeLeft);
9260         }
9261         fflush(serverMoves);
9262     }
9263
9264     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9265       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9266                         0, 1);
9267       return;
9268     }
9269     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9270     if (commentList[forwardMostMove+1] != NULL) {
9271         free(commentList[forwardMostMove+1]);
9272         commentList[forwardMostMove+1] = NULL;
9273     }
9274     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9275     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9276     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9277     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9278     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9279     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9280     gameInfo.result = GameUnfinished;
9281     if (gameInfo.resultDetails != NULL) {
9282         free(gameInfo.resultDetails);
9283         gameInfo.resultDetails = NULL;
9284     }
9285     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9286                               moveList[forwardMostMove - 1]);
9287     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9288                              PosFlags(forwardMostMove - 1),
9289                              fromY, fromX, toY, toX, promoChar,
9290                              parseList[forwardMostMove - 1]);
9291     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9292       case MT_NONE:
9293       case MT_STALEMATE:
9294       default:
9295         break;
9296       case MT_CHECK:
9297         if(gameInfo.variant != VariantShogi)
9298             strcat(parseList[forwardMostMove - 1], "+");
9299         break;
9300       case MT_CHECKMATE:
9301       case MT_STAINMATE:
9302         strcat(parseList[forwardMostMove - 1], "#");
9303         break;
9304     }
9305     if (appData.debugMode) {
9306         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9307     }
9308
9309 }
9310
9311 /* Updates currentMove if not pausing */
9312 void
9313 ShowMove(fromX, fromY, toX, toY)
9314 {
9315     int instant = (gameMode == PlayFromGameFile) ?
9316         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9317     if(appData.noGUI) return;
9318     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9319         if (!instant) {
9320             if (forwardMostMove == currentMove + 1) {
9321                 AnimateMove(boards[forwardMostMove - 1],
9322                             fromX, fromY, toX, toY);
9323             }
9324             if (appData.highlightLastMove) {
9325                 SetHighlights(fromX, fromY, toX, toY);
9326             }
9327         }
9328         currentMove = forwardMostMove;
9329     }
9330
9331     if (instant) return;
9332
9333     DisplayMove(currentMove - 1);
9334     DrawPosition(FALSE, boards[currentMove]);
9335     DisplayBothClocks();
9336     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9337     DisplayBook(currentMove);
9338 }
9339
9340 void SendEgtPath(ChessProgramState *cps)
9341 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9342         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9343
9344         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9345
9346         while(*p) {
9347             char c, *q = name+1, *r, *s;
9348
9349             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9350             while(*p && *p != ',') *q++ = *p++;
9351             *q++ = ':'; *q = 0;
9352             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9353                 strcmp(name, ",nalimov:") == 0 ) {
9354                 // take nalimov path from the menu-changeable option first, if it is defined
9355               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9356                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9357             } else
9358             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9359                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9360                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9361                 s = r = StrStr(s, ":") + 1; // beginning of path info
9362                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9363                 c = *r; *r = 0;             // temporarily null-terminate path info
9364                     *--q = 0;               // strip of trailig ':' from name
9365                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9366                 *r = c;
9367                 SendToProgram(buf,cps);     // send egtbpath command for this format
9368             }
9369             if(*p == ',') p++; // read away comma to position for next format name
9370         }
9371 }
9372
9373 void
9374 InitChessProgram(cps, setup)
9375      ChessProgramState *cps;
9376      int setup; /* [HGM] needed to setup FRC opening position */
9377 {
9378     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9379     if (appData.noChessProgram) return;
9380     hintRequested = FALSE;
9381     bookRequested = FALSE;
9382
9383     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9384     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9385     if(cps->memSize) { /* [HGM] memory */
9386       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9387         SendToProgram(buf, cps);
9388     }
9389     SendEgtPath(cps); /* [HGM] EGT */
9390     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9391       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9392         SendToProgram(buf, cps);
9393     }
9394
9395     SendToProgram(cps->initString, cps);
9396     if (gameInfo.variant != VariantNormal &&
9397         gameInfo.variant != VariantLoadable
9398         /* [HGM] also send variant if board size non-standard */
9399         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9400                                             ) {
9401       char *v = VariantName(gameInfo.variant);
9402       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9403         /* [HGM] in protocol 1 we have to assume all variants valid */
9404         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9405         DisplayFatalError(buf, 0, 1);
9406         return;
9407       }
9408
9409       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9410       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9411       if( gameInfo.variant == VariantXiangqi )
9412            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9413       if( gameInfo.variant == VariantShogi )
9414            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9415       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9416            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9417       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9418           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9419            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9420       if( gameInfo.variant == VariantCourier )
9421            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9422       if( gameInfo.variant == VariantSuper )
9423            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9424       if( gameInfo.variant == VariantGreat )
9425            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9426       if( gameInfo.variant == VariantSChess )
9427            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9428
9429       if(overruled) {
9430         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9431                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9432            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9433            if(StrStr(cps->variants, b) == NULL) {
9434                // specific sized variant not known, check if general sizing allowed
9435                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9436                    if(StrStr(cps->variants, "boardsize") == NULL) {
9437                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9438                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9439                        DisplayFatalError(buf, 0, 1);
9440                        return;
9441                    }
9442                    /* [HGM] here we really should compare with the maximum supported board size */
9443                }
9444            }
9445       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9446       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9447       SendToProgram(buf, cps);
9448     }
9449     currentlyInitializedVariant = gameInfo.variant;
9450
9451     /* [HGM] send opening position in FRC to first engine */
9452     if(setup) {
9453           SendToProgram("force\n", cps);
9454           SendBoard(cps, 0);
9455           /* engine is now in force mode! Set flag to wake it up after first move. */
9456           setboardSpoiledMachineBlack = 1;
9457     }
9458
9459     if (cps->sendICS) {
9460       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9461       SendToProgram(buf, cps);
9462     }
9463     cps->maybeThinking = FALSE;
9464     cps->offeredDraw = 0;
9465     if (!appData.icsActive) {
9466         SendTimeControl(cps, movesPerSession, timeControl,
9467                         timeIncrement, appData.searchDepth,
9468                         searchTime);
9469     }
9470     if (appData.showThinking
9471         // [HGM] thinking: four options require thinking output to be sent
9472         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9473                                 ) {
9474         SendToProgram("post\n", cps);
9475     }
9476     SendToProgram("hard\n", cps);
9477     if (!appData.ponderNextMove) {
9478         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9479            it without being sure what state we are in first.  "hard"
9480            is not a toggle, so that one is OK.
9481          */
9482         SendToProgram("easy\n", cps);
9483     }
9484     if (cps->usePing) {
9485       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9486       SendToProgram(buf, cps);
9487     }
9488     cps->initDone = TRUE;
9489 }
9490
9491
9492 void
9493 StartChessProgram(cps)
9494      ChessProgramState *cps;
9495 {
9496     char buf[MSG_SIZ];
9497     int err;
9498
9499     if (appData.noChessProgram) return;
9500     cps->initDone = FALSE;
9501
9502     if (strcmp(cps->host, "localhost") == 0) {
9503         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9504     } else if (*appData.remoteShell == NULLCHAR) {
9505         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9506     } else {
9507         if (*appData.remoteUser == NULLCHAR) {
9508           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9509                     cps->program);
9510         } else {
9511           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9512                     cps->host, appData.remoteUser, cps->program);
9513         }
9514         err = StartChildProcess(buf, "", &cps->pr);
9515     }
9516
9517     if (err != 0) {
9518       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9519         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9520         if(cps != &first) return;
9521         appData.noChessProgram = TRUE;
9522         ThawUI();
9523         SetNCPMode();
9524 //      DisplayFatalError(buf, err, 1);
9525 //      cps->pr = NoProc;
9526 //      cps->isr = NULL;
9527         return;
9528     }
9529
9530     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9531     if (cps->protocolVersion > 1) {
9532       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9533       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9534       cps->comboCnt = 0;  //                and values of combo boxes
9535       SendToProgram(buf, cps);
9536     } else {
9537       SendToProgram("xboard\n", cps);
9538     }
9539 }
9540
9541 void
9542 TwoMachinesEventIfReady P((void))
9543 {
9544   static int curMess = 0;
9545   if (first.lastPing != first.lastPong) {
9546     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9547     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9548     return;
9549   }
9550   if (second.lastPing != second.lastPong) {
9551     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9552     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9553     return;
9554   }
9555   DisplayMessage("", ""); curMess = 0;
9556   ThawUI();
9557   TwoMachinesEvent();
9558 }
9559
9560 char *
9561 MakeName(char *template)
9562 {
9563     time_t clock;
9564     struct tm *tm;
9565     static char buf[MSG_SIZ];
9566     char *p = buf;
9567     int i;
9568
9569     clock = time((time_t *)NULL);
9570     tm = localtime(&clock);
9571
9572     while(*p++ = *template++) if(p[-1] == '%') {
9573         switch(*template++) {
9574           case 0:   *p = 0; return buf;
9575           case 'Y': i = tm->tm_year+1900; break;
9576           case 'y': i = tm->tm_year-100; break;
9577           case 'M': i = tm->tm_mon+1; break;
9578           case 'd': i = tm->tm_mday; break;
9579           case 'h': i = tm->tm_hour; break;
9580           case 'm': i = tm->tm_min; break;
9581           case 's': i = tm->tm_sec; break;
9582           default:  i = 0;
9583         }
9584         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9585     }
9586     return buf;
9587 }
9588
9589 int
9590 CountPlayers(char *p)
9591 {
9592     int n = 0;
9593     while(p = strchr(p, '\n')) p++, n++; // count participants
9594     return n;
9595 }
9596
9597 FILE *
9598 WriteTourneyFile(char *results)
9599 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9600     FILE *f = fopen(appData.tourneyFile, "w");
9601     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9602         // create a file with tournament description
9603         fprintf(f, "-participants {%s}\n", appData.participants);
9604         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9605         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9606         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9607         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9608         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9609         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9610         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9611         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9612         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9613         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9614         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9615         if(searchTime > 0)
9616                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9617         else {
9618                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9619                 fprintf(f, "-tc %s\n", appData.timeControl);
9620                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9621         }
9622         fprintf(f, "-results \"%s\"\n", results);
9623     }
9624     return f;
9625 }
9626
9627 int
9628 CreateTourney(char *name)
9629 {
9630         FILE *f;
9631         if(name[0] == NULLCHAR) {
9632             if(appData.participants[0])
9633                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9634             return 0;
9635         }
9636         f = fopen(name, "r");
9637         if(f) { // file exists
9638             ASSIGN(appData.tourneyFile, name);
9639             ParseArgsFromFile(f); // parse it
9640         } else {
9641             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9642             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9643                 DisplayError(_("Not enough participants"), 0);
9644                 return 0;
9645             }
9646             ASSIGN(appData.tourneyFile, name);
9647             if((f = WriteTourneyFile("")) == NULL) return 0;
9648         }
9649         fclose(f);
9650         appData.noChessProgram = FALSE;
9651         appData.clockMode = TRUE;
9652         SetGNUMode();
9653         return 1;
9654 }
9655
9656 #define MAXENGINES 1000
9657 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9658
9659 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9660 {
9661     char buf[MSG_SIZ], *p, *q;
9662     int i=1;
9663     while(*names) {
9664         p = names; q = buf;
9665         while(*p && *p != '\n') *q++ = *p++;
9666         *q = 0;
9667         if(engineList[i]) free(engineList[i]);
9668         engineList[i] = strdup(buf);
9669         if(*p == '\n') p++;
9670         TidyProgramName(engineList[i], "localhost", buf);
9671         if(engineMnemonic[i]) free(engineMnemonic[i]);
9672         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9673             strcat(buf, " (");
9674             sscanf(q + 8, "%s", buf + strlen(buf));
9675             strcat(buf, ")");
9676         }
9677         engineMnemonic[i] = strdup(buf);
9678         names = p; i++;
9679       if(i > MAXENGINES - 2) break;
9680     }
9681     engineList[i] = NULL;
9682 }
9683
9684 // following implemented as macro to avoid type limitations
9685 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9686
9687 void SwapEngines(int n)
9688 {   // swap settings for first engine and other engine (so far only some selected options)
9689     int h;
9690     char *p;
9691     if(n == 0) return;
9692     SWAP(directory, p)
9693     SWAP(chessProgram, p)
9694     SWAP(isUCI, h)
9695     SWAP(hasOwnBookUCI, h)
9696     SWAP(protocolVersion, h)
9697     SWAP(reuse, h)
9698     SWAP(scoreIsAbsolute, h)
9699     SWAP(timeOdds, h)
9700     SWAP(logo, p)
9701     SWAP(pgnName, p)
9702 }
9703
9704 void
9705 SetPlayer(int player)
9706 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9707     int i;
9708     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9709     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9710     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9711     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9712     if(mnemonic[i]) {
9713         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9714         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9715         ParseArgsFromString(buf);
9716     }
9717     free(engineName);
9718 }
9719
9720 int
9721 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9722 {   // determine players from game number
9723     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9724
9725     if(appData.tourneyType == 0) {
9726         roundsPerCycle = (nPlayers - 1) | 1;
9727         pairingsPerRound = nPlayers / 2;
9728     } else if(appData.tourneyType > 0) {
9729         roundsPerCycle = nPlayers - appData.tourneyType;
9730         pairingsPerRound = appData.tourneyType;
9731     }
9732     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9733     gamesPerCycle = gamesPerRound * roundsPerCycle;
9734     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9735     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9736     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9737     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9738     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9739     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9740
9741     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9742     if(appData.roundSync) *syncInterval = gamesPerRound;
9743
9744     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9745
9746     if(appData.tourneyType == 0) {
9747         if(curPairing == (nPlayers-1)/2 ) {
9748             *whitePlayer = curRound;
9749             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9750         } else {
9751             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9752             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9753             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9754             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9755         }
9756     } else if(appData.tourneyType > 0) {
9757         *whitePlayer = curPairing;
9758         *blackPlayer = curRound + appData.tourneyType;
9759     }
9760
9761     // take care of white/black alternation per round. 
9762     // For cycles and games this is already taken care of by default, derived from matchGame!
9763     return curRound & 1;
9764 }
9765
9766 int
9767 NextTourneyGame(int nr, int *swapColors)
9768 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9769     char *p, *q;
9770     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9771     FILE *tf;
9772     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9773     tf = fopen(appData.tourneyFile, "r");
9774     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9775     ParseArgsFromFile(tf); fclose(tf);
9776     InitTimeControls(); // TC might be altered from tourney file
9777
9778     nPlayers = CountPlayers(appData.participants); // count participants
9779     if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9780         if(nr>=0 && !pairingReceived) {
9781             char buf[1<<16];
9782             if(pairing.pr == NoProc) StartChessProgram(&pairing);
9783             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9784             SendToProgram(buf, &pairing);
9785             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9786             SendToProgram(buf, &pairing);
9787             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9788         }
9789         pairingReceived = 0;                              // ... so we continue here 
9790         syncInterval = nPlayers/2; *swapColors = 0;
9791         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9792         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9793         matchGame = 1; roundNr = nr / syncInterval + 1;
9794     } else
9795     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9796
9797     if(syncInterval) {
9798         p = q = appData.results;
9799         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9800         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9801             DisplayMessage(_("Waiting for other game(s)"),"");
9802             waitingForGame = TRUE;
9803             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9804             return 0;
9805         }
9806         waitingForGame = FALSE;
9807     }
9808
9809     if(first.pr != NoProc) return 1; // engines already loaded
9810
9811     // redefine engines, engine dir, etc.
9812     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9813     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9814     SwapEngines(1);
9815     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9816     SwapEngines(1);         // and make that valid for second engine by swapping
9817     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9818     InitEngine(&second, 1);
9819     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9820     return 1;
9821 }
9822
9823 void
9824 NextMatchGame()
9825 {   // performs game initialization that does not invoke engines, and then tries to start the game
9826     int firstWhite, swapColors = 0;
9827     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9828     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9829     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9830     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9831     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9832     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9833     Reset(FALSE, first.pr != NoProc);
9834     appData.noChessProgram = FALSE;
9835     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9836     TwoMachinesEvent();
9837 }
9838
9839 void UserAdjudicationEvent( int result )
9840 {
9841     ChessMove gameResult = GameIsDrawn;
9842
9843     if( result > 0 ) {
9844         gameResult = WhiteWins;
9845     }
9846     else if( result < 0 ) {
9847         gameResult = BlackWins;
9848     }
9849
9850     if( gameMode == TwoMachinesPlay ) {
9851         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9852     }
9853 }
9854
9855
9856 // [HGM] save: calculate checksum of game to make games easily identifiable
9857 int StringCheckSum(char *s)
9858 {
9859         int i = 0;
9860         if(s==NULL) return 0;
9861         while(*s) i = i*259 + *s++;
9862         return i;
9863 }
9864
9865 int GameCheckSum()
9866 {
9867         int i, sum=0;
9868         for(i=backwardMostMove; i<forwardMostMove; i++) {
9869                 sum += pvInfoList[i].depth;
9870                 sum += StringCheckSum(parseList[i]);
9871                 sum += StringCheckSum(commentList[i]);
9872                 sum *= 261;
9873         }
9874         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9875         return sum + StringCheckSum(commentList[i]);
9876 } // end of save patch
9877
9878 void
9879 GameEnds(result, resultDetails, whosays)
9880      ChessMove result;
9881      char *resultDetails;
9882      int whosays;
9883 {
9884     GameMode nextGameMode;
9885     int isIcsGame;
9886     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9887
9888     if(endingGame) return; /* [HGM] crash: forbid recursion */
9889     endingGame = 1;
9890     if(twoBoards) { // [HGM] dual: switch back to one board
9891         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9892         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9893     }
9894     if (appData.debugMode) {
9895       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9896               result, resultDetails ? resultDetails : "(null)", whosays);
9897     }
9898
9899     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9900
9901     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9902         /* If we are playing on ICS, the server decides when the
9903            game is over, but the engine can offer to draw, claim
9904            a draw, or resign.
9905          */
9906 #if ZIPPY
9907         if (appData.zippyPlay && first.initDone) {
9908             if (result == GameIsDrawn) {
9909                 /* In case draw still needs to be claimed */
9910                 SendToICS(ics_prefix);
9911                 SendToICS("draw\n");
9912             } else if (StrCaseStr(resultDetails, "resign")) {
9913                 SendToICS(ics_prefix);
9914                 SendToICS("resign\n");
9915             }
9916         }
9917 #endif
9918         endingGame = 0; /* [HGM] crash */
9919         return;
9920     }
9921
9922     /* If we're loading the game from a file, stop */
9923     if (whosays == GE_FILE) {
9924       (void) StopLoadGameTimer();
9925       gameFileFP = NULL;
9926     }
9927
9928     /* Cancel draw offers */
9929     first.offeredDraw = second.offeredDraw = 0;
9930
9931     /* If this is an ICS game, only ICS can really say it's done;
9932        if not, anyone can. */
9933     isIcsGame = (gameMode == IcsPlayingWhite ||
9934                  gameMode == IcsPlayingBlack ||
9935                  gameMode == IcsObserving    ||
9936                  gameMode == IcsExamining);
9937
9938     if (!isIcsGame || whosays == GE_ICS) {
9939         /* OK -- not an ICS game, or ICS said it was done */
9940         StopClocks();
9941         if (!isIcsGame && !appData.noChessProgram)
9942           SetUserThinkingEnables();
9943
9944         /* [HGM] if a machine claims the game end we verify this claim */
9945         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9946             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9947                 char claimer;
9948                 ChessMove trueResult = (ChessMove) -1;
9949
9950                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9951                                             first.twoMachinesColor[0] :
9952                                             second.twoMachinesColor[0] ;
9953
9954                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9955                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9956                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9957                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9958                 } else
9959                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9960                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9961                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9962                 } else
9963                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9964                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9965                 }
9966
9967                 // now verify win claims, but not in drop games, as we don't understand those yet
9968                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9969                                                  || gameInfo.variant == VariantGreat) &&
9970                     (result == WhiteWins && claimer == 'w' ||
9971                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9972                       if (appData.debugMode) {
9973                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9974                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9975                       }
9976                       if(result != trueResult) {
9977                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9978                               result = claimer == 'w' ? BlackWins : WhiteWins;
9979                               resultDetails = buf;
9980                       }
9981                 } else
9982                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9983                     && (forwardMostMove <= backwardMostMove ||
9984                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9985                         (claimer=='b')==(forwardMostMove&1))
9986                                                                                   ) {
9987                       /* [HGM] verify: draws that were not flagged are false claims */
9988                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9989                       result = claimer == 'w' ? BlackWins : WhiteWins;
9990                       resultDetails = buf;
9991                 }
9992                 /* (Claiming a loss is accepted no questions asked!) */
9993             }
9994             /* [HGM] bare: don't allow bare King to win */
9995             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9996                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9997                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9998                && result != GameIsDrawn)
9999             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10000                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10001                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10002                         if(p >= 0 && p <= (int)WhiteKing) k++;
10003                 }
10004                 if (appData.debugMode) {
10005                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10006                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10007                 }
10008                 if(k <= 1) {
10009                         result = GameIsDrawn;
10010                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10011                         resultDetails = buf;
10012                 }
10013             }
10014         }
10015
10016
10017         if(serverMoves != NULL && !loadFlag) { char c = '=';
10018             if(result==WhiteWins) c = '+';
10019             if(result==BlackWins) c = '-';
10020             if(resultDetails != NULL)
10021                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10022         }
10023         if (resultDetails != NULL) {
10024             gameInfo.result = result;
10025             gameInfo.resultDetails = StrSave(resultDetails);
10026
10027             /* display last move only if game was not loaded from file */
10028             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10029                 DisplayMove(currentMove - 1);
10030
10031             if (forwardMostMove != 0) {
10032                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10033                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10034                                                                 ) {
10035                     if (*appData.saveGameFile != NULLCHAR) {
10036                         SaveGameToFile(appData.saveGameFile, TRUE);
10037                     } else if (appData.autoSaveGames) {
10038                         AutoSaveGame();
10039                     }
10040                     if (*appData.savePositionFile != NULLCHAR) {
10041                         SavePositionToFile(appData.savePositionFile);
10042                     }
10043                 }
10044             }
10045
10046             /* Tell program how game ended in case it is learning */
10047             /* [HGM] Moved this to after saving the PGN, just in case */
10048             /* engine died and we got here through time loss. In that */
10049             /* case we will get a fatal error writing the pipe, which */
10050             /* would otherwise lose us the PGN.                       */
10051             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10052             /* output during GameEnds should never be fatal anymore   */
10053             if (gameMode == MachinePlaysWhite ||
10054                 gameMode == MachinePlaysBlack ||
10055                 gameMode == TwoMachinesPlay ||
10056                 gameMode == IcsPlayingWhite ||
10057                 gameMode == IcsPlayingBlack ||
10058                 gameMode == BeginningOfGame) {
10059                 char buf[MSG_SIZ];
10060                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10061                         resultDetails);
10062                 if (first.pr != NoProc) {
10063                     SendToProgram(buf, &first);
10064                 }
10065                 if (second.pr != NoProc &&
10066                     gameMode == TwoMachinesPlay) {
10067                     SendToProgram(buf, &second);
10068                 }
10069             }
10070         }
10071
10072         if (appData.icsActive) {
10073             if (appData.quietPlay &&
10074                 (gameMode == IcsPlayingWhite ||
10075                  gameMode == IcsPlayingBlack)) {
10076                 SendToICS(ics_prefix);
10077                 SendToICS("set shout 1\n");
10078             }
10079             nextGameMode = IcsIdle;
10080             ics_user_moved = FALSE;
10081             /* clean up premove.  It's ugly when the game has ended and the
10082              * premove highlights are still on the board.
10083              */
10084             if (gotPremove) {
10085               gotPremove = FALSE;
10086               ClearPremoveHighlights();
10087               DrawPosition(FALSE, boards[currentMove]);
10088             }
10089             if (whosays == GE_ICS) {
10090                 switch (result) {
10091                 case WhiteWins:
10092                     if (gameMode == IcsPlayingWhite)
10093                         PlayIcsWinSound();
10094                     else if(gameMode == IcsPlayingBlack)
10095                         PlayIcsLossSound();
10096                     break;
10097                 case BlackWins:
10098                     if (gameMode == IcsPlayingBlack)
10099                         PlayIcsWinSound();
10100                     else if(gameMode == IcsPlayingWhite)
10101                         PlayIcsLossSound();
10102                     break;
10103                 case GameIsDrawn:
10104                     PlayIcsDrawSound();
10105                     break;
10106                 default:
10107                     PlayIcsUnfinishedSound();
10108                 }
10109             }
10110         } else if (gameMode == EditGame ||
10111                    gameMode == PlayFromGameFile ||
10112                    gameMode == AnalyzeMode ||
10113                    gameMode == AnalyzeFile) {
10114             nextGameMode = gameMode;
10115         } else {
10116             nextGameMode = EndOfGame;
10117         }
10118         pausing = FALSE;
10119         ModeHighlight();
10120     } else {
10121         nextGameMode = gameMode;
10122     }
10123
10124     if (appData.noChessProgram) {
10125         gameMode = nextGameMode;
10126         ModeHighlight();
10127         endingGame = 0; /* [HGM] crash */
10128         return;
10129     }
10130
10131     if (first.reuse) {
10132         /* Put first chess program into idle state */
10133         if (first.pr != NoProc &&
10134             (gameMode == MachinePlaysWhite ||
10135              gameMode == MachinePlaysBlack ||
10136              gameMode == TwoMachinesPlay ||
10137              gameMode == IcsPlayingWhite ||
10138              gameMode == IcsPlayingBlack ||
10139              gameMode == BeginningOfGame)) {
10140             SendToProgram("force\n", &first);
10141             if (first.usePing) {
10142               char buf[MSG_SIZ];
10143               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10144               SendToProgram(buf, &first);
10145             }
10146         }
10147     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10148         /* Kill off first chess program */
10149         if (first.isr != NULL)
10150           RemoveInputSource(first.isr);
10151         first.isr = NULL;
10152
10153         if (first.pr != NoProc) {
10154             ExitAnalyzeMode();
10155             DoSleep( appData.delayBeforeQuit );
10156             SendToProgram("quit\n", &first);
10157             DoSleep( appData.delayAfterQuit );
10158             DestroyChildProcess(first.pr, first.useSigterm);
10159         }
10160         first.pr = NoProc;
10161     }
10162     if (second.reuse) {
10163         /* Put second chess program into idle state */
10164         if (second.pr != NoProc &&
10165             gameMode == TwoMachinesPlay) {
10166             SendToProgram("force\n", &second);
10167             if (second.usePing) {
10168               char buf[MSG_SIZ];
10169               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10170               SendToProgram(buf, &second);
10171             }
10172         }
10173     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10174         /* Kill off second chess program */
10175         if (second.isr != NULL)
10176           RemoveInputSource(second.isr);
10177         second.isr = NULL;
10178
10179         if (second.pr != NoProc) {
10180             DoSleep( appData.delayBeforeQuit );
10181             SendToProgram("quit\n", &second);
10182             DoSleep( appData.delayAfterQuit );
10183             DestroyChildProcess(second.pr, second.useSigterm);
10184         }
10185         second.pr = NoProc;
10186     }
10187
10188     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10189         char resChar = '=';
10190         switch (result) {
10191         case WhiteWins:
10192           resChar = '+';
10193           if (first.twoMachinesColor[0] == 'w') {
10194             first.matchWins++;
10195           } else {
10196             second.matchWins++;
10197           }
10198           break;
10199         case BlackWins:
10200           resChar = '-';
10201           if (first.twoMachinesColor[0] == 'b') {
10202             first.matchWins++;
10203           } else {
10204             second.matchWins++;
10205           }
10206           break;
10207         case GameUnfinished:
10208           resChar = ' ';
10209         default:
10210           break;
10211         }
10212
10213         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10214         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10215             ReserveGame(nextGame, resChar); // sets nextGame
10216             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10217             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10218         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10219
10220         if (nextGame <= appData.matchGames && !abortMatch) {
10221             gameMode = nextGameMode;
10222             matchGame = nextGame; // this will be overruled in tourney mode!
10223             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10224             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10225             endingGame = 0; /* [HGM] crash */
10226             return;
10227         } else {
10228             gameMode = nextGameMode;
10229             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10230                      first.tidy, second.tidy,
10231                      first.matchWins, second.matchWins,
10232                      appData.matchGames - (first.matchWins + second.matchWins));
10233             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10234             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10235                 first.twoMachinesColor = "black\n";
10236                 second.twoMachinesColor = "white\n";
10237             } else {
10238                 first.twoMachinesColor = "white\n";
10239                 second.twoMachinesColor = "black\n";
10240             }
10241         }
10242     }
10243     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10244         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10245       ExitAnalyzeMode();
10246     gameMode = nextGameMode;
10247     ModeHighlight();
10248     endingGame = 0;  /* [HGM] crash */
10249     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10250         if(matchMode == TRUE) { // match through command line: exit with or without popup
10251             if(ranking) {
10252                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10253                 else ExitEvent(0);
10254             } else DisplayFatalError(buf, 0, 0);
10255         } else { // match through menu; just stop, with or without popup
10256             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10257             if(ranking){
10258                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10259             } else DisplayNote(buf);
10260       }
10261       if(ranking) free(ranking);
10262     }
10263 }
10264
10265 /* Assumes program was just initialized (initString sent).
10266    Leaves program in force mode. */
10267 void
10268 FeedMovesToProgram(cps, upto)
10269      ChessProgramState *cps;
10270      int upto;
10271 {
10272     int i;
10273
10274     if (appData.debugMode)
10275       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10276               startedFromSetupPosition ? "position and " : "",
10277               backwardMostMove, upto, cps->which);
10278     if(currentlyInitializedVariant != gameInfo.variant) {
10279       char buf[MSG_SIZ];
10280         // [HGM] variantswitch: make engine aware of new variant
10281         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10282                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10283         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10284         SendToProgram(buf, cps);
10285         currentlyInitializedVariant = gameInfo.variant;
10286     }
10287     SendToProgram("force\n", cps);
10288     if (startedFromSetupPosition) {
10289         SendBoard(cps, backwardMostMove);
10290     if (appData.debugMode) {
10291         fprintf(debugFP, "feedMoves\n");
10292     }
10293     }
10294     for (i = backwardMostMove; i < upto; i++) {
10295         SendMoveToProgram(i, cps);
10296     }
10297 }
10298
10299
10300 int
10301 ResurrectChessProgram()
10302 {
10303      /* The chess program may have exited.
10304         If so, restart it and feed it all the moves made so far. */
10305     static int doInit = 0;
10306
10307     if (appData.noChessProgram) return 1;
10308
10309     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10310         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10311         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10312         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10313     } else {
10314         if (first.pr != NoProc) return 1;
10315         StartChessProgram(&first);
10316     }
10317     InitChessProgram(&first, FALSE);
10318     FeedMovesToProgram(&first, currentMove);
10319
10320     if (!first.sendTime) {
10321         /* can't tell gnuchess what its clock should read,
10322            so we bow to its notion. */
10323         ResetClocks();
10324         timeRemaining[0][currentMove] = whiteTimeRemaining;
10325         timeRemaining[1][currentMove] = blackTimeRemaining;
10326     }
10327
10328     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10329                 appData.icsEngineAnalyze) && first.analysisSupport) {
10330       SendToProgram("analyze\n", &first);
10331       first.analyzing = TRUE;
10332     }
10333     return 1;
10334 }
10335
10336 /*
10337  * Button procedures
10338  */
10339 void
10340 Reset(redraw, init)
10341      int redraw, init;
10342 {
10343     int i;
10344
10345     if (appData.debugMode) {
10346         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10347                 redraw, init, gameMode);
10348     }
10349     CleanupTail(); // [HGM] vari: delete any stored variations
10350     pausing = pauseExamInvalid = FALSE;
10351     startedFromSetupPosition = blackPlaysFirst = FALSE;
10352     firstMove = TRUE;
10353     whiteFlag = blackFlag = FALSE;
10354     userOfferedDraw = FALSE;
10355     hintRequested = bookRequested = FALSE;
10356     first.maybeThinking = FALSE;
10357     second.maybeThinking = FALSE;
10358     first.bookSuspend = FALSE; // [HGM] book
10359     second.bookSuspend = FALSE;
10360     thinkOutput[0] = NULLCHAR;
10361     lastHint[0] = NULLCHAR;
10362     ClearGameInfo(&gameInfo);
10363     gameInfo.variant = StringToVariant(appData.variant);
10364     ics_user_moved = ics_clock_paused = FALSE;
10365     ics_getting_history = H_FALSE;
10366     ics_gamenum = -1;
10367     white_holding[0] = black_holding[0] = NULLCHAR;
10368     ClearProgramStats();
10369     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10370
10371     ResetFrontEnd();
10372     ClearHighlights();
10373     flipView = appData.flipView;
10374     ClearPremoveHighlights();
10375     gotPremove = FALSE;
10376     alarmSounded = FALSE;
10377
10378     GameEnds(EndOfFile, NULL, GE_PLAYER);
10379     if(appData.serverMovesName != NULL) {
10380         /* [HGM] prepare to make moves file for broadcasting */
10381         clock_t t = clock();
10382         if(serverMoves != NULL) fclose(serverMoves);
10383         serverMoves = fopen(appData.serverMovesName, "r");
10384         if(serverMoves != NULL) {
10385             fclose(serverMoves);
10386             /* delay 15 sec before overwriting, so all clients can see end */
10387             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10388         }
10389         serverMoves = fopen(appData.serverMovesName, "w");
10390     }
10391
10392     ExitAnalyzeMode();
10393     gameMode = BeginningOfGame;
10394     ModeHighlight();
10395     if(appData.icsActive) gameInfo.variant = VariantNormal;
10396     currentMove = forwardMostMove = backwardMostMove = 0;
10397     InitPosition(redraw);
10398     for (i = 0; i < MAX_MOVES; i++) {
10399         if (commentList[i] != NULL) {
10400             free(commentList[i]);
10401             commentList[i] = NULL;
10402         }
10403     }
10404     ResetClocks();
10405     timeRemaining[0][0] = whiteTimeRemaining;
10406     timeRemaining[1][0] = blackTimeRemaining;
10407
10408     if (first.pr == NULL) {
10409         StartChessProgram(&first);
10410     }
10411     if (init) {
10412             InitChessProgram(&first, startedFromSetupPosition);
10413     }
10414     DisplayTitle("");
10415     DisplayMessage("", "");
10416     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10417     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10418 }
10419
10420 void
10421 AutoPlayGameLoop()
10422 {
10423     for (;;) {
10424         if (!AutoPlayOneMove())
10425           return;
10426         if (matchMode || appData.timeDelay == 0)
10427           continue;
10428         if (appData.timeDelay < 0)
10429           return;
10430         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10431         break;
10432     }
10433 }
10434
10435
10436 int
10437 AutoPlayOneMove()
10438 {
10439     int fromX, fromY, toX, toY;
10440
10441     if (appData.debugMode) {
10442       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10443     }
10444
10445     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10446       return FALSE;
10447
10448     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10449       pvInfoList[currentMove].depth = programStats.depth;
10450       pvInfoList[currentMove].score = programStats.score;
10451       pvInfoList[currentMove].time  = 0;
10452       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10453     }
10454
10455     if (currentMove >= forwardMostMove) {
10456       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10457       gameMode = EditGame;
10458       ModeHighlight();
10459
10460       /* [AS] Clear current move marker at the end of a game */
10461       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10462
10463       return FALSE;
10464     }
10465
10466     toX = moveList[currentMove][2] - AAA;
10467     toY = moveList[currentMove][3] - ONE;
10468
10469     if (moveList[currentMove][1] == '@') {
10470         if (appData.highlightLastMove) {
10471             SetHighlights(-1, -1, toX, toY);
10472         }
10473     } else {
10474         fromX = moveList[currentMove][0] - AAA;
10475         fromY = moveList[currentMove][1] - ONE;
10476
10477         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10478
10479         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10480
10481         if (appData.highlightLastMove) {
10482             SetHighlights(fromX, fromY, toX, toY);
10483         }
10484     }
10485     DisplayMove(currentMove);
10486     SendMoveToProgram(currentMove++, &first);
10487     DisplayBothClocks();
10488     DrawPosition(FALSE, boards[currentMove]);
10489     // [HGM] PV info: always display, routine tests if empty
10490     DisplayComment(currentMove - 1, commentList[currentMove]);
10491     return TRUE;
10492 }
10493
10494
10495 int
10496 LoadGameOneMove(readAhead)
10497      ChessMove readAhead;
10498 {
10499     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10500     char promoChar = NULLCHAR;
10501     ChessMove moveType;
10502     char move[MSG_SIZ];
10503     char *p, *q;
10504
10505     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10506         gameMode != AnalyzeMode && gameMode != Training) {
10507         gameFileFP = NULL;
10508         return FALSE;
10509     }
10510
10511     yyboardindex = forwardMostMove;
10512     if (readAhead != EndOfFile) {
10513       moveType = readAhead;
10514     } else {
10515       if (gameFileFP == NULL)
10516           return FALSE;
10517       moveType = (ChessMove) Myylex();
10518     }
10519
10520     done = FALSE;
10521     switch (moveType) {
10522       case Comment:
10523         if (appData.debugMode)
10524           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10525         p = yy_text;
10526
10527         /* append the comment but don't display it */
10528         AppendComment(currentMove, p, FALSE);
10529         return TRUE;
10530
10531       case WhiteCapturesEnPassant:
10532       case BlackCapturesEnPassant:
10533       case WhitePromotion:
10534       case BlackPromotion:
10535       case WhiteNonPromotion:
10536       case BlackNonPromotion:
10537       case NormalMove:
10538       case WhiteKingSideCastle:
10539       case WhiteQueenSideCastle:
10540       case BlackKingSideCastle:
10541       case BlackQueenSideCastle:
10542       case WhiteKingSideCastleWild:
10543       case WhiteQueenSideCastleWild:
10544       case BlackKingSideCastleWild:
10545       case BlackQueenSideCastleWild:
10546       /* PUSH Fabien */
10547       case WhiteHSideCastleFR:
10548       case WhiteASideCastleFR:
10549       case BlackHSideCastleFR:
10550       case BlackASideCastleFR:
10551       /* POP Fabien */
10552         if (appData.debugMode)
10553           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10554         fromX = currentMoveString[0] - AAA;
10555         fromY = currentMoveString[1] - ONE;
10556         toX = currentMoveString[2] - AAA;
10557         toY = currentMoveString[3] - ONE;
10558         promoChar = currentMoveString[4];
10559         break;
10560
10561       case WhiteDrop:
10562       case BlackDrop:
10563         if (appData.debugMode)
10564           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10565         fromX = moveType == WhiteDrop ?
10566           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10567         (int) CharToPiece(ToLower(currentMoveString[0]));
10568         fromY = DROP_RANK;
10569         toX = currentMoveString[2] - AAA;
10570         toY = currentMoveString[3] - ONE;
10571         break;
10572
10573       case WhiteWins:
10574       case BlackWins:
10575       case GameIsDrawn:
10576       case GameUnfinished:
10577         if (appData.debugMode)
10578           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10579         p = strchr(yy_text, '{');
10580         if (p == NULL) p = strchr(yy_text, '(');
10581         if (p == NULL) {
10582             p = yy_text;
10583             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10584         } else {
10585             q = strchr(p, *p == '{' ? '}' : ')');
10586             if (q != NULL) *q = NULLCHAR;
10587             p++;
10588         }
10589         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10590         GameEnds(moveType, p, GE_FILE);
10591         done = TRUE;
10592         if (cmailMsgLoaded) {
10593             ClearHighlights();
10594             flipView = WhiteOnMove(currentMove);
10595             if (moveType == GameUnfinished) flipView = !flipView;
10596             if (appData.debugMode)
10597               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10598         }
10599         break;
10600
10601       case EndOfFile:
10602         if (appData.debugMode)
10603           fprintf(debugFP, "Parser hit end of file\n");
10604         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10605           case MT_NONE:
10606           case MT_CHECK:
10607             break;
10608           case MT_CHECKMATE:
10609           case MT_STAINMATE:
10610             if (WhiteOnMove(currentMove)) {
10611                 GameEnds(BlackWins, "Black mates", GE_FILE);
10612             } else {
10613                 GameEnds(WhiteWins, "White mates", GE_FILE);
10614             }
10615             break;
10616           case MT_STALEMATE:
10617             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10618             break;
10619         }
10620         done = TRUE;
10621         break;
10622
10623       case MoveNumberOne:
10624         if (lastLoadGameStart == GNUChessGame) {
10625             /* GNUChessGames have numbers, but they aren't move numbers */
10626             if (appData.debugMode)
10627               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10628                       yy_text, (int) moveType);
10629             return LoadGameOneMove(EndOfFile); /* tail recursion */
10630         }
10631         /* else fall thru */
10632
10633       case XBoardGame:
10634       case GNUChessGame:
10635       case PGNTag:
10636         /* Reached start of next game in file */
10637         if (appData.debugMode)
10638           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10639         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10640           case MT_NONE:
10641           case MT_CHECK:
10642             break;
10643           case MT_CHECKMATE:
10644           case MT_STAINMATE:
10645             if (WhiteOnMove(currentMove)) {
10646                 GameEnds(BlackWins, "Black mates", GE_FILE);
10647             } else {
10648                 GameEnds(WhiteWins, "White mates", GE_FILE);
10649             }
10650             break;
10651           case MT_STALEMATE:
10652             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10653             break;
10654         }
10655         done = TRUE;
10656         break;
10657
10658       case PositionDiagram:     /* should not happen; ignore */
10659       case ElapsedTime:         /* ignore */
10660       case NAG:                 /* ignore */
10661         if (appData.debugMode)
10662           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10663                   yy_text, (int) moveType);
10664         return LoadGameOneMove(EndOfFile); /* tail recursion */
10665
10666       case IllegalMove:
10667         if (appData.testLegality) {
10668             if (appData.debugMode)
10669               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10670             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10671                     (forwardMostMove / 2) + 1,
10672                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10673             DisplayError(move, 0);
10674             done = TRUE;
10675         } else {
10676             if (appData.debugMode)
10677               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10678                       yy_text, currentMoveString);
10679             fromX = currentMoveString[0] - AAA;
10680             fromY = currentMoveString[1] - ONE;
10681             toX = currentMoveString[2] - AAA;
10682             toY = currentMoveString[3] - ONE;
10683             promoChar = currentMoveString[4];
10684         }
10685         break;
10686
10687       case AmbiguousMove:
10688         if (appData.debugMode)
10689           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10690         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10691                 (forwardMostMove / 2) + 1,
10692                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10693         DisplayError(move, 0);
10694         done = TRUE;
10695         break;
10696
10697       default:
10698       case ImpossibleMove:
10699         if (appData.debugMode)
10700           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10701         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10702                 (forwardMostMove / 2) + 1,
10703                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10704         DisplayError(move, 0);
10705         done = TRUE;
10706         break;
10707     }
10708
10709     if (done) {
10710         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10711             DrawPosition(FALSE, boards[currentMove]);
10712             DisplayBothClocks();
10713             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10714               DisplayComment(currentMove - 1, commentList[currentMove]);
10715         }
10716         (void) StopLoadGameTimer();
10717         gameFileFP = NULL;
10718         cmailOldMove = forwardMostMove;
10719         return FALSE;
10720     } else {
10721         /* currentMoveString is set as a side-effect of yylex */
10722
10723         thinkOutput[0] = NULLCHAR;
10724         MakeMove(fromX, fromY, toX, toY, promoChar);
10725         currentMove = forwardMostMove;
10726         return TRUE;
10727     }
10728 }
10729
10730 /* Load the nth game from the given file */
10731 int
10732 LoadGameFromFile(filename, n, title, useList)
10733      char *filename;
10734      int n;
10735      char *title;
10736      /*Boolean*/ int useList;
10737 {
10738     FILE *f;
10739     char buf[MSG_SIZ];
10740
10741     if (strcmp(filename, "-") == 0) {
10742         f = stdin;
10743         title = "stdin";
10744     } else {
10745         f = fopen(filename, "rb");
10746         if (f == NULL) {
10747           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10748             DisplayError(buf, errno);
10749             return FALSE;
10750         }
10751     }
10752     if (fseek(f, 0, 0) == -1) {
10753         /* f is not seekable; probably a pipe */
10754         useList = FALSE;
10755     }
10756     if (useList && n == 0) {
10757         int error = GameListBuild(f);
10758         if (error) {
10759             DisplayError(_("Cannot build game list"), error);
10760         } else if (!ListEmpty(&gameList) &&
10761                    ((ListGame *) gameList.tailPred)->number > 1) {
10762             GameListPopUp(f, title);
10763             return TRUE;
10764         }
10765         GameListDestroy();
10766         n = 1;
10767     }
10768     if (n == 0) n = 1;
10769     return LoadGame(f, n, title, FALSE);
10770 }
10771
10772
10773 void
10774 MakeRegisteredMove()
10775 {
10776     int fromX, fromY, toX, toY;
10777     char promoChar;
10778     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10779         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10780           case CMAIL_MOVE:
10781           case CMAIL_DRAW:
10782             if (appData.debugMode)
10783               fprintf(debugFP, "Restoring %s for game %d\n",
10784                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10785
10786             thinkOutput[0] = NULLCHAR;
10787             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10788             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10789             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10790             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10791             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10792             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10793             MakeMove(fromX, fromY, toX, toY, promoChar);
10794             ShowMove(fromX, fromY, toX, toY);
10795
10796             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10797               case MT_NONE:
10798               case MT_CHECK:
10799                 break;
10800
10801               case MT_CHECKMATE:
10802               case MT_STAINMATE:
10803                 if (WhiteOnMove(currentMove)) {
10804                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10805                 } else {
10806                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10807                 }
10808                 break;
10809
10810               case MT_STALEMATE:
10811                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10812                 break;
10813             }
10814
10815             break;
10816
10817           case CMAIL_RESIGN:
10818             if (WhiteOnMove(currentMove)) {
10819                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10820             } else {
10821                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10822             }
10823             break;
10824
10825           case CMAIL_ACCEPT:
10826             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10827             break;
10828
10829           default:
10830             break;
10831         }
10832     }
10833
10834     return;
10835 }
10836
10837 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10838 int
10839 CmailLoadGame(f, gameNumber, title, useList)
10840      FILE *f;
10841      int gameNumber;
10842      char *title;
10843      int useList;
10844 {
10845     int retVal;
10846
10847     if (gameNumber > nCmailGames) {
10848         DisplayError(_("No more games in this message"), 0);
10849         return FALSE;
10850     }
10851     if (f == lastLoadGameFP) {
10852         int offset = gameNumber - lastLoadGameNumber;
10853         if (offset == 0) {
10854             cmailMsg[0] = NULLCHAR;
10855             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10856                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10857                 nCmailMovesRegistered--;
10858             }
10859             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10860             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10861                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10862             }
10863         } else {
10864             if (! RegisterMove()) return FALSE;
10865         }
10866     }
10867
10868     retVal = LoadGame(f, gameNumber, title, useList);
10869
10870     /* Make move registered during previous look at this game, if any */
10871     MakeRegisteredMove();
10872
10873     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10874         commentList[currentMove]
10875           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10876         DisplayComment(currentMove - 1, commentList[currentMove]);
10877     }
10878
10879     return retVal;
10880 }
10881
10882 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10883 int
10884 ReloadGame(offset)
10885      int offset;
10886 {
10887     int gameNumber = lastLoadGameNumber + offset;
10888     if (lastLoadGameFP == NULL) {
10889         DisplayError(_("No game has been loaded yet"), 0);
10890         return FALSE;
10891     }
10892     if (gameNumber <= 0) {
10893         DisplayError(_("Can't back up any further"), 0);
10894         return FALSE;
10895     }
10896     if (cmailMsgLoaded) {
10897         return CmailLoadGame(lastLoadGameFP, gameNumber,
10898                              lastLoadGameTitle, lastLoadGameUseList);
10899     } else {
10900         return LoadGame(lastLoadGameFP, gameNumber,
10901                         lastLoadGameTitle, lastLoadGameUseList);
10902     }
10903 }
10904
10905
10906
10907 /* Load the nth game from open file f */
10908 int
10909 LoadGame(f, gameNumber, title, useList)
10910      FILE *f;
10911      int gameNumber;
10912      char *title;
10913      int useList;
10914 {
10915     ChessMove cm;
10916     char buf[MSG_SIZ];
10917     int gn = gameNumber;
10918     ListGame *lg = NULL;
10919     int numPGNTags = 0;
10920     int err;
10921     GameMode oldGameMode;
10922     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10923
10924     if (appData.debugMode)
10925         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10926
10927     if (gameMode == Training )
10928         SetTrainingModeOff();
10929
10930     oldGameMode = gameMode;
10931     if (gameMode != BeginningOfGame) {
10932       Reset(FALSE, TRUE);
10933     }
10934
10935     gameFileFP = f;
10936     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10937         fclose(lastLoadGameFP);
10938     }
10939
10940     if (useList) {
10941         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10942
10943         if (lg) {
10944             fseek(f, lg->offset, 0);
10945             GameListHighlight(gameNumber);
10946             gn = 1;
10947         }
10948         else {
10949             DisplayError(_("Game number out of range"), 0);
10950             return FALSE;
10951         }
10952     } else {
10953         GameListDestroy();
10954         if (fseek(f, 0, 0) == -1) {
10955             if (f == lastLoadGameFP ?
10956                 gameNumber == lastLoadGameNumber + 1 :
10957                 gameNumber == 1) {
10958                 gn = 1;
10959             } else {
10960                 DisplayError(_("Can't seek on game file"), 0);
10961                 return FALSE;
10962             }
10963         }
10964     }
10965     lastLoadGameFP = f;
10966     lastLoadGameNumber = gameNumber;
10967     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10968     lastLoadGameUseList = useList;
10969
10970     yynewfile(f);
10971
10972     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10973       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10974                 lg->gameInfo.black);
10975             DisplayTitle(buf);
10976     } else if (*title != NULLCHAR) {
10977         if (gameNumber > 1) {
10978           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10979             DisplayTitle(buf);
10980         } else {
10981             DisplayTitle(title);
10982         }
10983     }
10984
10985     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10986         gameMode = PlayFromGameFile;
10987         ModeHighlight();
10988     }
10989
10990     currentMove = forwardMostMove = backwardMostMove = 0;
10991     CopyBoard(boards[0], initialPosition);
10992     StopClocks();
10993
10994     /*
10995      * Skip the first gn-1 games in the file.
10996      * Also skip over anything that precedes an identifiable
10997      * start of game marker, to avoid being confused by
10998      * garbage at the start of the file.  Currently
10999      * recognized start of game markers are the move number "1",
11000      * the pattern "gnuchess .* game", the pattern
11001      * "^[#;%] [^ ]* game file", and a PGN tag block.
11002      * A game that starts with one of the latter two patterns
11003      * will also have a move number 1, possibly
11004      * following a position diagram.
11005      * 5-4-02: Let's try being more lenient and allowing a game to
11006      * start with an unnumbered move.  Does that break anything?
11007      */
11008     cm = lastLoadGameStart = EndOfFile;
11009     while (gn > 0) {
11010         yyboardindex = forwardMostMove;
11011         cm = (ChessMove) Myylex();
11012         switch (cm) {
11013           case EndOfFile:
11014             if (cmailMsgLoaded) {
11015                 nCmailGames = CMAIL_MAX_GAMES - gn;
11016             } else {
11017                 Reset(TRUE, TRUE);
11018                 DisplayError(_("Game not found in file"), 0);
11019             }
11020             return FALSE;
11021
11022           case GNUChessGame:
11023           case XBoardGame:
11024             gn--;
11025             lastLoadGameStart = cm;
11026             break;
11027
11028           case MoveNumberOne:
11029             switch (lastLoadGameStart) {
11030               case GNUChessGame:
11031               case XBoardGame:
11032               case PGNTag:
11033                 break;
11034               case MoveNumberOne:
11035               case EndOfFile:
11036                 gn--;           /* count this game */
11037                 lastLoadGameStart = cm;
11038                 break;
11039               default:
11040                 /* impossible */
11041                 break;
11042             }
11043             break;
11044
11045           case PGNTag:
11046             switch (lastLoadGameStart) {
11047               case GNUChessGame:
11048               case PGNTag:
11049               case MoveNumberOne:
11050               case EndOfFile:
11051                 gn--;           /* count this game */
11052                 lastLoadGameStart = cm;
11053                 break;
11054               case XBoardGame:
11055                 lastLoadGameStart = cm; /* game counted already */
11056                 break;
11057               default:
11058                 /* impossible */
11059                 break;
11060             }
11061             if (gn > 0) {
11062                 do {
11063                     yyboardindex = forwardMostMove;
11064                     cm = (ChessMove) Myylex();
11065                 } while (cm == PGNTag || cm == Comment);
11066             }
11067             break;
11068
11069           case WhiteWins:
11070           case BlackWins:
11071           case GameIsDrawn:
11072             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11073                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11074                     != CMAIL_OLD_RESULT) {
11075                     nCmailResults ++ ;
11076                     cmailResult[  CMAIL_MAX_GAMES
11077                                 - gn - 1] = CMAIL_OLD_RESULT;
11078                 }
11079             }
11080             break;
11081
11082           case NormalMove:
11083             /* Only a NormalMove can be at the start of a game
11084              * without a position diagram. */
11085             if (lastLoadGameStart == EndOfFile ) {
11086               gn--;
11087               lastLoadGameStart = MoveNumberOne;
11088             }
11089             break;
11090
11091           default:
11092             break;
11093         }
11094     }
11095
11096     if (appData.debugMode)
11097       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11098
11099     if (cm == XBoardGame) {
11100         /* Skip any header junk before position diagram and/or move 1 */
11101         for (;;) {
11102             yyboardindex = forwardMostMove;
11103             cm = (ChessMove) Myylex();
11104
11105             if (cm == EndOfFile ||
11106                 cm == GNUChessGame || cm == XBoardGame) {
11107                 /* Empty game; pretend end-of-file and handle later */
11108                 cm = EndOfFile;
11109                 break;
11110             }
11111
11112             if (cm == MoveNumberOne || cm == PositionDiagram ||
11113                 cm == PGNTag || cm == Comment)
11114               break;
11115         }
11116     } else if (cm == GNUChessGame) {
11117         if (gameInfo.event != NULL) {
11118             free(gameInfo.event);
11119         }
11120         gameInfo.event = StrSave(yy_text);
11121     }
11122
11123     startedFromSetupPosition = FALSE;
11124     while (cm == PGNTag) {
11125         if (appData.debugMode)
11126           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11127         err = ParsePGNTag(yy_text, &gameInfo);
11128         if (!err) numPGNTags++;
11129
11130         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11131         if(gameInfo.variant != oldVariant) {
11132             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11133             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11134             InitPosition(TRUE);
11135             oldVariant = gameInfo.variant;
11136             if (appData.debugMode)
11137               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11138         }
11139
11140
11141         if (gameInfo.fen != NULL) {
11142           Board initial_position;
11143           startedFromSetupPosition = TRUE;
11144           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11145             Reset(TRUE, TRUE);
11146             DisplayError(_("Bad FEN position in file"), 0);
11147             return FALSE;
11148           }
11149           CopyBoard(boards[0], initial_position);
11150           if (blackPlaysFirst) {
11151             currentMove = forwardMostMove = backwardMostMove = 1;
11152             CopyBoard(boards[1], initial_position);
11153             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11154             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11155             timeRemaining[0][1] = whiteTimeRemaining;
11156             timeRemaining[1][1] = blackTimeRemaining;
11157             if (commentList[0] != NULL) {
11158               commentList[1] = commentList[0];
11159               commentList[0] = NULL;
11160             }
11161           } else {
11162             currentMove = forwardMostMove = backwardMostMove = 0;
11163           }
11164           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11165           {   int i;
11166               initialRulePlies = FENrulePlies;
11167               for( i=0; i< nrCastlingRights; i++ )
11168                   initialRights[i] = initial_position[CASTLING][i];
11169           }
11170           yyboardindex = forwardMostMove;
11171           free(gameInfo.fen);
11172           gameInfo.fen = NULL;
11173         }
11174
11175         yyboardindex = forwardMostMove;
11176         cm = (ChessMove) Myylex();
11177
11178         /* Handle comments interspersed among the tags */
11179         while (cm == Comment) {
11180             char *p;
11181             if (appData.debugMode)
11182               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11183             p = yy_text;
11184             AppendComment(currentMove, p, FALSE);
11185             yyboardindex = forwardMostMove;
11186             cm = (ChessMove) Myylex();
11187         }
11188     }
11189
11190     /* don't rely on existence of Event tag since if game was
11191      * pasted from clipboard the Event tag may not exist
11192      */
11193     if (numPGNTags > 0){
11194         char *tags;
11195         if (gameInfo.variant == VariantNormal) {
11196           VariantClass v = StringToVariant(gameInfo.event);
11197           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11198           if(v < VariantShogi) gameInfo.variant = v;
11199         }
11200         if (!matchMode) {
11201           if( appData.autoDisplayTags ) {
11202             tags = PGNTags(&gameInfo);
11203             TagsPopUp(tags, CmailMsg());
11204             free(tags);
11205           }
11206         }
11207     } else {
11208         /* Make something up, but don't display it now */
11209         SetGameInfo();
11210         TagsPopDown();
11211     }
11212
11213     if (cm == PositionDiagram) {
11214         int i, j;
11215         char *p;
11216         Board initial_position;
11217
11218         if (appData.debugMode)
11219           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11220
11221         if (!startedFromSetupPosition) {
11222             p = yy_text;
11223             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11224               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11225                 switch (*p) {
11226                   case '{':
11227                   case '[':
11228                   case '-':
11229                   case ' ':
11230                   case '\t':
11231                   case '\n':
11232                   case '\r':
11233                     break;
11234                   default:
11235                     initial_position[i][j++] = CharToPiece(*p);
11236                     break;
11237                 }
11238             while (*p == ' ' || *p == '\t' ||
11239                    *p == '\n' || *p == '\r') p++;
11240
11241             if (strncmp(p, "black", strlen("black"))==0)
11242               blackPlaysFirst = TRUE;
11243             else
11244               blackPlaysFirst = FALSE;
11245             startedFromSetupPosition = TRUE;
11246
11247             CopyBoard(boards[0], initial_position);
11248             if (blackPlaysFirst) {
11249                 currentMove = forwardMostMove = backwardMostMove = 1;
11250                 CopyBoard(boards[1], initial_position);
11251                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11252                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11253                 timeRemaining[0][1] = whiteTimeRemaining;
11254                 timeRemaining[1][1] = blackTimeRemaining;
11255                 if (commentList[0] != NULL) {
11256                     commentList[1] = commentList[0];
11257                     commentList[0] = NULL;
11258                 }
11259             } else {
11260                 currentMove = forwardMostMove = backwardMostMove = 0;
11261             }
11262         }
11263         yyboardindex = forwardMostMove;
11264         cm = (ChessMove) Myylex();
11265     }
11266
11267     if (first.pr == NoProc) {
11268         StartChessProgram(&first);
11269     }
11270     InitChessProgram(&first, FALSE);
11271     SendToProgram("force\n", &first);
11272     if (startedFromSetupPosition) {
11273         SendBoard(&first, forwardMostMove);
11274     if (appData.debugMode) {
11275         fprintf(debugFP, "Load Game\n");
11276     }
11277         DisplayBothClocks();
11278     }
11279
11280     /* [HGM] server: flag to write setup moves in broadcast file as one */
11281     loadFlag = appData.suppressLoadMoves;
11282
11283     while (cm == Comment) {
11284         char *p;
11285         if (appData.debugMode)
11286           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11287         p = yy_text;
11288         AppendComment(currentMove, p, FALSE);
11289         yyboardindex = forwardMostMove;
11290         cm = (ChessMove) Myylex();
11291     }
11292
11293     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11294         cm == WhiteWins || cm == BlackWins ||
11295         cm == GameIsDrawn || cm == GameUnfinished) {
11296         DisplayMessage("", _("No moves in game"));
11297         if (cmailMsgLoaded) {
11298             if (appData.debugMode)
11299               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11300             ClearHighlights();
11301             flipView = FALSE;
11302         }
11303         DrawPosition(FALSE, boards[currentMove]);
11304         DisplayBothClocks();
11305         gameMode = EditGame;
11306         ModeHighlight();
11307         gameFileFP = NULL;
11308         cmailOldMove = 0;
11309         return TRUE;
11310     }
11311
11312     // [HGM] PV info: routine tests if comment empty
11313     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11314         DisplayComment(currentMove - 1, commentList[currentMove]);
11315     }
11316     if (!matchMode && appData.timeDelay != 0)
11317       DrawPosition(FALSE, boards[currentMove]);
11318
11319     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11320       programStats.ok_to_send = 1;
11321     }
11322
11323     /* if the first token after the PGN tags is a move
11324      * and not move number 1, retrieve it from the parser
11325      */
11326     if (cm != MoveNumberOne)
11327         LoadGameOneMove(cm);
11328
11329     /* load the remaining moves from the file */
11330     while (LoadGameOneMove(EndOfFile)) {
11331       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11332       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11333     }
11334
11335     /* rewind to the start of the game */
11336     currentMove = backwardMostMove;
11337
11338     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11339
11340     if (oldGameMode == AnalyzeFile ||
11341         oldGameMode == AnalyzeMode) {
11342       AnalyzeFileEvent();
11343     }
11344
11345     if (matchMode || appData.timeDelay == 0) {
11346       ToEndEvent();
11347       gameMode = EditGame;
11348       ModeHighlight();
11349     } else if (appData.timeDelay > 0) {
11350       AutoPlayGameLoop();
11351     }
11352
11353     if (appData.debugMode)
11354         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11355
11356     loadFlag = 0; /* [HGM] true game starts */
11357     return TRUE;
11358 }
11359
11360 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11361 int
11362 ReloadPosition(offset)
11363      int offset;
11364 {
11365     int positionNumber = lastLoadPositionNumber + offset;
11366     if (lastLoadPositionFP == NULL) {
11367         DisplayError(_("No position has been loaded yet"), 0);
11368         return FALSE;
11369     }
11370     if (positionNumber <= 0) {
11371         DisplayError(_("Can't back up any further"), 0);
11372         return FALSE;
11373     }
11374     return LoadPosition(lastLoadPositionFP, positionNumber,
11375                         lastLoadPositionTitle);
11376 }
11377
11378 /* Load the nth position from the given file */
11379 int
11380 LoadPositionFromFile(filename, n, title)
11381      char *filename;
11382      int n;
11383      char *title;
11384 {
11385     FILE *f;
11386     char buf[MSG_SIZ];
11387
11388     if (strcmp(filename, "-") == 0) {
11389         return LoadPosition(stdin, n, "stdin");
11390     } else {
11391         f = fopen(filename, "rb");
11392         if (f == NULL) {
11393             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11394             DisplayError(buf, errno);
11395             return FALSE;
11396         } else {
11397             return LoadPosition(f, n, title);
11398         }
11399     }
11400 }
11401
11402 /* Load the nth position from the given open file, and close it */
11403 int
11404 LoadPosition(f, positionNumber, title)
11405      FILE *f;
11406      int positionNumber;
11407      char *title;
11408 {
11409     char *p, line[MSG_SIZ];
11410     Board initial_position;
11411     int i, j, fenMode, pn;
11412
11413     if (gameMode == Training )
11414         SetTrainingModeOff();
11415
11416     if (gameMode != BeginningOfGame) {
11417         Reset(FALSE, TRUE);
11418     }
11419     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11420         fclose(lastLoadPositionFP);
11421     }
11422     if (positionNumber == 0) positionNumber = 1;
11423     lastLoadPositionFP = f;
11424     lastLoadPositionNumber = positionNumber;
11425     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11426     if (first.pr == NoProc) {
11427       StartChessProgram(&first);
11428       InitChessProgram(&first, FALSE);
11429     }
11430     pn = positionNumber;
11431     if (positionNumber < 0) {
11432         /* Negative position number means to seek to that byte offset */
11433         if (fseek(f, -positionNumber, 0) == -1) {
11434             DisplayError(_("Can't seek on position file"), 0);
11435             return FALSE;
11436         };
11437         pn = 1;
11438     } else {
11439         if (fseek(f, 0, 0) == -1) {
11440             if (f == lastLoadPositionFP ?
11441                 positionNumber == lastLoadPositionNumber + 1 :
11442                 positionNumber == 1) {
11443                 pn = 1;
11444             } else {
11445                 DisplayError(_("Can't seek on position file"), 0);
11446                 return FALSE;
11447             }
11448         }
11449     }
11450     /* See if this file is FEN or old-style xboard */
11451     if (fgets(line, MSG_SIZ, f) == NULL) {
11452         DisplayError(_("Position not found in file"), 0);
11453         return FALSE;
11454     }
11455     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11456     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11457
11458     if (pn >= 2) {
11459         if (fenMode || line[0] == '#') pn--;
11460         while (pn > 0) {
11461             /* skip positions before number pn */
11462             if (fgets(line, MSG_SIZ, f) == NULL) {
11463                 Reset(TRUE, TRUE);
11464                 DisplayError(_("Position not found in file"), 0);
11465                 return FALSE;
11466             }
11467             if (fenMode || line[0] == '#') pn--;
11468         }
11469     }
11470
11471     if (fenMode) {
11472         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11473             DisplayError(_("Bad FEN position in file"), 0);
11474             return FALSE;
11475         }
11476     } else {
11477         (void) fgets(line, MSG_SIZ, f);
11478         (void) fgets(line, MSG_SIZ, f);
11479
11480         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11481             (void) fgets(line, MSG_SIZ, f);
11482             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11483                 if (*p == ' ')
11484                   continue;
11485                 initial_position[i][j++] = CharToPiece(*p);
11486             }
11487         }
11488
11489         blackPlaysFirst = FALSE;
11490         if (!feof(f)) {
11491             (void) fgets(line, MSG_SIZ, f);
11492             if (strncmp(line, "black", strlen("black"))==0)
11493               blackPlaysFirst = TRUE;
11494         }
11495     }
11496     startedFromSetupPosition = TRUE;
11497
11498     SendToProgram("force\n", &first);
11499     CopyBoard(boards[0], initial_position);
11500     if (blackPlaysFirst) {
11501         currentMove = forwardMostMove = backwardMostMove = 1;
11502         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11503         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11504         CopyBoard(boards[1], initial_position);
11505         DisplayMessage("", _("Black to play"));
11506     } else {
11507         currentMove = forwardMostMove = backwardMostMove = 0;
11508         DisplayMessage("", _("White to play"));
11509     }
11510     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11511     SendBoard(&first, forwardMostMove);
11512     if (appData.debugMode) {
11513 int i, j;
11514   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11515   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11516         fprintf(debugFP, "Load Position\n");
11517     }
11518
11519     if (positionNumber > 1) {
11520       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11521         DisplayTitle(line);
11522     } else {
11523         DisplayTitle(title);
11524     }
11525     gameMode = EditGame;
11526     ModeHighlight();
11527     ResetClocks();
11528     timeRemaining[0][1] = whiteTimeRemaining;
11529     timeRemaining[1][1] = blackTimeRemaining;
11530     DrawPosition(FALSE, boards[currentMove]);
11531
11532     return TRUE;
11533 }
11534
11535
11536 void
11537 CopyPlayerNameIntoFileName(dest, src)
11538      char **dest, *src;
11539 {
11540     while (*src != NULLCHAR && *src != ',') {
11541         if (*src == ' ') {
11542             *(*dest)++ = '_';
11543             src++;
11544         } else {
11545             *(*dest)++ = *src++;
11546         }
11547     }
11548 }
11549
11550 char *DefaultFileName(ext)
11551      char *ext;
11552 {
11553     static char def[MSG_SIZ];
11554     char *p;
11555
11556     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11557         p = def;
11558         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11559         *p++ = '-';
11560         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11561         *p++ = '.';
11562         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11563     } else {
11564         def[0] = NULLCHAR;
11565     }
11566     return def;
11567 }
11568
11569 /* Save the current game to the given file */
11570 int
11571 SaveGameToFile(filename, append)
11572      char *filename;
11573      int append;
11574 {
11575     FILE *f;
11576     char buf[MSG_SIZ];
11577     int result;
11578
11579     if (strcmp(filename, "-") == 0) {
11580         return SaveGame(stdout, 0, NULL);
11581     } else {
11582         f = fopen(filename, append ? "a" : "w");
11583         if (f == NULL) {
11584             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11585             DisplayError(buf, errno);
11586             return FALSE;
11587         } else {
11588             safeStrCpy(buf, lastMsg, MSG_SIZ);
11589             DisplayMessage(_("Waiting for access to save file"), "");
11590             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11591             DisplayMessage(_("Saving game"), "");
11592             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11593             result = SaveGame(f, 0, NULL);
11594             DisplayMessage(buf, "");
11595             return result;
11596         }
11597     }
11598 }
11599
11600 char *
11601 SavePart(str)
11602      char *str;
11603 {
11604     static char buf[MSG_SIZ];
11605     char *p;
11606
11607     p = strchr(str, ' ');
11608     if (p == NULL) return str;
11609     strncpy(buf, str, p - str);
11610     buf[p - str] = NULLCHAR;
11611     return buf;
11612 }
11613
11614 #define PGN_MAX_LINE 75
11615
11616 #define PGN_SIDE_WHITE  0
11617 #define PGN_SIDE_BLACK  1
11618
11619 /* [AS] */
11620 static int FindFirstMoveOutOfBook( int side )
11621 {
11622     int result = -1;
11623
11624     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11625         int index = backwardMostMove;
11626         int has_book_hit = 0;
11627
11628         if( (index % 2) != side ) {
11629             index++;
11630         }
11631
11632         while( index < forwardMostMove ) {
11633             /* Check to see if engine is in book */
11634             int depth = pvInfoList[index].depth;
11635             int score = pvInfoList[index].score;
11636             int in_book = 0;
11637
11638             if( depth <= 2 ) {
11639                 in_book = 1;
11640             }
11641             else if( score == 0 && depth == 63 ) {
11642                 in_book = 1; /* Zappa */
11643             }
11644             else if( score == 2 && depth == 99 ) {
11645                 in_book = 1; /* Abrok */
11646             }
11647
11648             has_book_hit += in_book;
11649
11650             if( ! in_book ) {
11651                 result = index;
11652
11653                 break;
11654             }
11655
11656             index += 2;
11657         }
11658     }
11659
11660     return result;
11661 }
11662
11663 /* [AS] */
11664 void GetOutOfBookInfo( char * buf )
11665 {
11666     int oob[2];
11667     int i;
11668     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11669
11670     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11671     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11672
11673     *buf = '\0';
11674
11675     if( oob[0] >= 0 || oob[1] >= 0 ) {
11676         for( i=0; i<2; i++ ) {
11677             int idx = oob[i];
11678
11679             if( idx >= 0 ) {
11680                 if( i > 0 && oob[0] >= 0 ) {
11681                     strcat( buf, "   " );
11682                 }
11683
11684                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11685                 sprintf( buf+strlen(buf), "%s%.2f",
11686                     pvInfoList[idx].score >= 0 ? "+" : "",
11687                     pvInfoList[idx].score / 100.0 );
11688             }
11689         }
11690     }
11691 }
11692
11693 /* Save game in PGN style and close the file */
11694 int
11695 SaveGamePGN(f)
11696      FILE *f;
11697 {
11698     int i, offset, linelen, newblock;
11699     time_t tm;
11700 //    char *movetext;
11701     char numtext[32];
11702     int movelen, numlen, blank;
11703     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11704
11705     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11706
11707     tm = time((time_t *) NULL);
11708
11709     PrintPGNTags(f, &gameInfo);
11710
11711     if (backwardMostMove > 0 || startedFromSetupPosition) {
11712         char *fen = PositionToFEN(backwardMostMove, NULL);
11713         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11714         fprintf(f, "\n{--------------\n");
11715         PrintPosition(f, backwardMostMove);
11716         fprintf(f, "--------------}\n");
11717         free(fen);
11718     }
11719     else {
11720         /* [AS] Out of book annotation */
11721         if( appData.saveOutOfBookInfo ) {
11722             char buf[64];
11723
11724             GetOutOfBookInfo( buf );
11725
11726             if( buf[0] != '\0' ) {
11727                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11728             }
11729         }
11730
11731         fprintf(f, "\n");
11732     }
11733
11734     i = backwardMostMove;
11735     linelen = 0;
11736     newblock = TRUE;
11737
11738     while (i < forwardMostMove) {
11739         /* Print comments preceding this move */
11740         if (commentList[i] != NULL) {
11741             if (linelen > 0) fprintf(f, "\n");
11742             fprintf(f, "%s", commentList[i]);
11743             linelen = 0;
11744             newblock = TRUE;
11745         }
11746
11747         /* Format move number */
11748         if ((i % 2) == 0)
11749           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11750         else
11751           if (newblock)
11752             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11753           else
11754             numtext[0] = NULLCHAR;
11755
11756         numlen = strlen(numtext);
11757         newblock = FALSE;
11758
11759         /* Print move number */
11760         blank = linelen > 0 && numlen > 0;
11761         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11762             fprintf(f, "\n");
11763             linelen = 0;
11764             blank = 0;
11765         }
11766         if (blank) {
11767             fprintf(f, " ");
11768             linelen++;
11769         }
11770         fprintf(f, "%s", numtext);
11771         linelen += numlen;
11772
11773         /* Get move */
11774         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11775         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11776
11777         /* Print move */
11778         blank = linelen > 0 && movelen > 0;
11779         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11780             fprintf(f, "\n");
11781             linelen = 0;
11782             blank = 0;
11783         }
11784         if (blank) {
11785             fprintf(f, " ");
11786             linelen++;
11787         }
11788         fprintf(f, "%s", move_buffer);
11789         linelen += movelen;
11790
11791         /* [AS] Add PV info if present */
11792         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11793             /* [HGM] add time */
11794             char buf[MSG_SIZ]; int seconds;
11795
11796             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11797
11798             if( seconds <= 0)
11799               buf[0] = 0;
11800             else
11801               if( seconds < 30 )
11802                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11803               else
11804                 {
11805                   seconds = (seconds + 4)/10; // round to full seconds
11806                   if( seconds < 60 )
11807                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11808                   else
11809                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11810                 }
11811
11812             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11813                       pvInfoList[i].score >= 0 ? "+" : "",
11814                       pvInfoList[i].score / 100.0,
11815                       pvInfoList[i].depth,
11816                       buf );
11817
11818             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11819
11820             /* Print score/depth */
11821             blank = linelen > 0 && movelen > 0;
11822             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11823                 fprintf(f, "\n");
11824                 linelen = 0;
11825                 blank = 0;
11826             }
11827             if (blank) {
11828                 fprintf(f, " ");
11829                 linelen++;
11830             }
11831             fprintf(f, "%s", move_buffer);
11832             linelen += movelen;
11833         }
11834
11835         i++;
11836     }
11837
11838     /* Start a new line */
11839     if (linelen > 0) fprintf(f, "\n");
11840
11841     /* Print comments after last move */
11842     if (commentList[i] != NULL) {
11843         fprintf(f, "%s\n", commentList[i]);
11844     }
11845
11846     /* Print result */
11847     if (gameInfo.resultDetails != NULL &&
11848         gameInfo.resultDetails[0] != NULLCHAR) {
11849         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11850                 PGNResult(gameInfo.result));
11851     } else {
11852         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11853     }
11854
11855     fclose(f);
11856     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11857     return TRUE;
11858 }
11859
11860 /* Save game in old style and close the file */
11861 int
11862 SaveGameOldStyle(f)
11863      FILE *f;
11864 {
11865     int i, offset;
11866     time_t tm;
11867
11868     tm = time((time_t *) NULL);
11869
11870     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11871     PrintOpponents(f);
11872
11873     if (backwardMostMove > 0 || startedFromSetupPosition) {
11874         fprintf(f, "\n[--------------\n");
11875         PrintPosition(f, backwardMostMove);
11876         fprintf(f, "--------------]\n");
11877     } else {
11878         fprintf(f, "\n");
11879     }
11880
11881     i = backwardMostMove;
11882     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11883
11884     while (i < forwardMostMove) {
11885         if (commentList[i] != NULL) {
11886             fprintf(f, "[%s]\n", commentList[i]);
11887         }
11888
11889         if ((i % 2) == 1) {
11890             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11891             i++;
11892         } else {
11893             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11894             i++;
11895             if (commentList[i] != NULL) {
11896                 fprintf(f, "\n");
11897                 continue;
11898             }
11899             if (i >= forwardMostMove) {
11900                 fprintf(f, "\n");
11901                 break;
11902             }
11903             fprintf(f, "%s\n", parseList[i]);
11904             i++;
11905         }
11906     }
11907
11908     if (commentList[i] != NULL) {
11909         fprintf(f, "[%s]\n", commentList[i]);
11910     }
11911
11912     /* This isn't really the old style, but it's close enough */
11913     if (gameInfo.resultDetails != NULL &&
11914         gameInfo.resultDetails[0] != NULLCHAR) {
11915         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11916                 gameInfo.resultDetails);
11917     } else {
11918         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11919     }
11920
11921     fclose(f);
11922     return TRUE;
11923 }
11924
11925 /* Save the current game to open file f and close the file */
11926 int
11927 SaveGame(f, dummy, dummy2)
11928      FILE *f;
11929      int dummy;
11930      char *dummy2;
11931 {
11932     if (gameMode == EditPosition) EditPositionDone(TRUE);
11933     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11934     if (appData.oldSaveStyle)
11935       return SaveGameOldStyle(f);
11936     else
11937       return SaveGamePGN(f);
11938 }
11939
11940 /* Save the current position to the given file */
11941 int
11942 SavePositionToFile(filename)
11943      char *filename;
11944 {
11945     FILE *f;
11946     char buf[MSG_SIZ];
11947
11948     if (strcmp(filename, "-") == 0) {
11949         return SavePosition(stdout, 0, NULL);
11950     } else {
11951         f = fopen(filename, "a");
11952         if (f == NULL) {
11953             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11954             DisplayError(buf, errno);
11955             return FALSE;
11956         } else {
11957             safeStrCpy(buf, lastMsg, MSG_SIZ);
11958             DisplayMessage(_("Waiting for access to save file"), "");
11959             flock(fileno(f), LOCK_EX); // [HGM] lock
11960             DisplayMessage(_("Saving position"), "");
11961             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11962             SavePosition(f, 0, NULL);
11963             DisplayMessage(buf, "");
11964             return TRUE;
11965         }
11966     }
11967 }
11968
11969 /* Save the current position to the given open file and close the file */
11970 int
11971 SavePosition(f, dummy, dummy2)
11972      FILE *f;
11973      int dummy;
11974      char *dummy2;
11975 {
11976     time_t tm;
11977     char *fen;
11978
11979     if (gameMode == EditPosition) EditPositionDone(TRUE);
11980     if (appData.oldSaveStyle) {
11981         tm = time((time_t *) NULL);
11982
11983         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11984         PrintOpponents(f);
11985         fprintf(f, "[--------------\n");
11986         PrintPosition(f, currentMove);
11987         fprintf(f, "--------------]\n");
11988     } else {
11989         fen = PositionToFEN(currentMove, NULL);
11990         fprintf(f, "%s\n", fen);
11991         free(fen);
11992     }
11993     fclose(f);
11994     return TRUE;
11995 }
11996
11997 void
11998 ReloadCmailMsgEvent(unregister)
11999      int unregister;
12000 {
12001 #if !WIN32
12002     static char *inFilename = NULL;
12003     static char *outFilename;
12004     int i;
12005     struct stat inbuf, outbuf;
12006     int status;
12007
12008     /* Any registered moves are unregistered if unregister is set, */
12009     /* i.e. invoked by the signal handler */
12010     if (unregister) {
12011         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12012             cmailMoveRegistered[i] = FALSE;
12013             if (cmailCommentList[i] != NULL) {
12014                 free(cmailCommentList[i]);
12015                 cmailCommentList[i] = NULL;
12016             }
12017         }
12018         nCmailMovesRegistered = 0;
12019     }
12020
12021     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12022         cmailResult[i] = CMAIL_NOT_RESULT;
12023     }
12024     nCmailResults = 0;
12025
12026     if (inFilename == NULL) {
12027         /* Because the filenames are static they only get malloced once  */
12028         /* and they never get freed                                      */
12029         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12030         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12031
12032         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12033         sprintf(outFilename, "%s.out", appData.cmailGameName);
12034     }
12035
12036     status = stat(outFilename, &outbuf);
12037     if (status < 0) {
12038         cmailMailedMove = FALSE;
12039     } else {
12040         status = stat(inFilename, &inbuf);
12041         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12042     }
12043
12044     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12045        counts the games, notes how each one terminated, etc.
12046
12047        It would be nice to remove this kludge and instead gather all
12048        the information while building the game list.  (And to keep it
12049        in the game list nodes instead of having a bunch of fixed-size
12050        parallel arrays.)  Note this will require getting each game's
12051        termination from the PGN tags, as the game list builder does
12052        not process the game moves.  --mann
12053        */
12054     cmailMsgLoaded = TRUE;
12055     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12056
12057     /* Load first game in the file or popup game menu */
12058     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12059
12060 #endif /* !WIN32 */
12061     return;
12062 }
12063
12064 int
12065 RegisterMove()
12066 {
12067     FILE *f;
12068     char string[MSG_SIZ];
12069
12070     if (   cmailMailedMove
12071         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12072         return TRUE;            /* Allow free viewing  */
12073     }
12074
12075     /* Unregister move to ensure that we don't leave RegisterMove        */
12076     /* with the move registered when the conditions for registering no   */
12077     /* longer hold                                                       */
12078     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12079         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12080         nCmailMovesRegistered --;
12081
12082         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12083           {
12084               free(cmailCommentList[lastLoadGameNumber - 1]);
12085               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12086           }
12087     }
12088
12089     if (cmailOldMove == -1) {
12090         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12091         return FALSE;
12092     }
12093
12094     if (currentMove > cmailOldMove + 1) {
12095         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12096         return FALSE;
12097     }
12098
12099     if (currentMove < cmailOldMove) {
12100         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12101         return FALSE;
12102     }
12103
12104     if (forwardMostMove > currentMove) {
12105         /* Silently truncate extra moves */
12106         TruncateGame();
12107     }
12108
12109     if (   (currentMove == cmailOldMove + 1)
12110         || (   (currentMove == cmailOldMove)
12111             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12112                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12113         if (gameInfo.result != GameUnfinished) {
12114             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12115         }
12116
12117         if (commentList[currentMove] != NULL) {
12118             cmailCommentList[lastLoadGameNumber - 1]
12119               = StrSave(commentList[currentMove]);
12120         }
12121         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12122
12123         if (appData.debugMode)
12124           fprintf(debugFP, "Saving %s for game %d\n",
12125                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12126
12127         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12128
12129         f = fopen(string, "w");
12130         if (appData.oldSaveStyle) {
12131             SaveGameOldStyle(f); /* also closes the file */
12132
12133             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12134             f = fopen(string, "w");
12135             SavePosition(f, 0, NULL); /* also closes the file */
12136         } else {
12137             fprintf(f, "{--------------\n");
12138             PrintPosition(f, currentMove);
12139             fprintf(f, "--------------}\n\n");
12140
12141             SaveGame(f, 0, NULL); /* also closes the file*/
12142         }
12143
12144         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12145         nCmailMovesRegistered ++;
12146     } else if (nCmailGames == 1) {
12147         DisplayError(_("You have not made a move yet"), 0);
12148         return FALSE;
12149     }
12150
12151     return TRUE;
12152 }
12153
12154 void
12155 MailMoveEvent()
12156 {
12157 #if !WIN32
12158     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12159     FILE *commandOutput;
12160     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12161     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12162     int nBuffers;
12163     int i;
12164     int archived;
12165     char *arcDir;
12166
12167     if (! cmailMsgLoaded) {
12168         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12169         return;
12170     }
12171
12172     if (nCmailGames == nCmailResults) {
12173         DisplayError(_("No unfinished games"), 0);
12174         return;
12175     }
12176
12177 #if CMAIL_PROHIBIT_REMAIL
12178     if (cmailMailedMove) {
12179       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);
12180         DisplayError(msg, 0);
12181         return;
12182     }
12183 #endif
12184
12185     if (! (cmailMailedMove || RegisterMove())) return;
12186
12187     if (   cmailMailedMove
12188         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12189       snprintf(string, MSG_SIZ, partCommandString,
12190                appData.debugMode ? " -v" : "", appData.cmailGameName);
12191         commandOutput = popen(string, "r");
12192
12193         if (commandOutput == NULL) {
12194             DisplayError(_("Failed to invoke cmail"), 0);
12195         } else {
12196             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12197                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12198             }
12199             if (nBuffers > 1) {
12200                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12201                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12202                 nBytes = MSG_SIZ - 1;
12203             } else {
12204                 (void) memcpy(msg, buffer, nBytes);
12205             }
12206             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12207
12208             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12209                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12210
12211                 archived = TRUE;
12212                 for (i = 0; i < nCmailGames; i ++) {
12213                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12214                         archived = FALSE;
12215                     }
12216                 }
12217                 if (   archived
12218                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12219                         != NULL)) {
12220                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12221                            arcDir,
12222                            appData.cmailGameName,
12223                            gameInfo.date);
12224                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12225                     cmailMsgLoaded = FALSE;
12226                 }
12227             }
12228
12229             DisplayInformation(msg);
12230             pclose(commandOutput);
12231         }
12232     } else {
12233         if ((*cmailMsg) != '\0') {
12234             DisplayInformation(cmailMsg);
12235         }
12236     }
12237
12238     return;
12239 #endif /* !WIN32 */
12240 }
12241
12242 char *
12243 CmailMsg()
12244 {
12245 #if WIN32
12246     return NULL;
12247 #else
12248     int  prependComma = 0;
12249     char number[5];
12250     char string[MSG_SIZ];       /* Space for game-list */
12251     int  i;
12252
12253     if (!cmailMsgLoaded) return "";
12254
12255     if (cmailMailedMove) {
12256       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12257     } else {
12258         /* Create a list of games left */
12259       snprintf(string, MSG_SIZ, "[");
12260         for (i = 0; i < nCmailGames; i ++) {
12261             if (! (   cmailMoveRegistered[i]
12262                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12263                 if (prependComma) {
12264                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12265                 } else {
12266                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12267                     prependComma = 1;
12268                 }
12269
12270                 strcat(string, number);
12271             }
12272         }
12273         strcat(string, "]");
12274
12275         if (nCmailMovesRegistered + nCmailResults == 0) {
12276             switch (nCmailGames) {
12277               case 1:
12278                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12279                 break;
12280
12281               case 2:
12282                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12283                 break;
12284
12285               default:
12286                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12287                          nCmailGames);
12288                 break;
12289             }
12290         } else {
12291             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12292               case 1:
12293                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12294                          string);
12295                 break;
12296
12297               case 0:
12298                 if (nCmailResults == nCmailGames) {
12299                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12300                 } else {
12301                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12302                 }
12303                 break;
12304
12305               default:
12306                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12307                          string);
12308             }
12309         }
12310     }
12311     return cmailMsg;
12312 #endif /* WIN32 */
12313 }
12314
12315 void
12316 ResetGameEvent()
12317 {
12318     if (gameMode == Training)
12319       SetTrainingModeOff();
12320
12321     Reset(TRUE, TRUE);
12322     cmailMsgLoaded = FALSE;
12323     if (appData.icsActive) {
12324       SendToICS(ics_prefix);
12325       SendToICS("refresh\n");
12326     }
12327 }
12328
12329 void
12330 ExitEvent(status)
12331      int status;
12332 {
12333     exiting++;
12334     if (exiting > 2) {
12335       /* Give up on clean exit */
12336       exit(status);
12337     }
12338     if (exiting > 1) {
12339       /* Keep trying for clean exit */
12340       return;
12341     }
12342
12343     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12344
12345     if (telnetISR != NULL) {
12346       RemoveInputSource(telnetISR);
12347     }
12348     if (icsPR != NoProc) {
12349       DestroyChildProcess(icsPR, TRUE);
12350     }
12351
12352     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12353     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12354
12355     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12356     /* make sure this other one finishes before killing it!                  */
12357     if(endingGame) { int count = 0;
12358         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12359         while(endingGame && count++ < 10) DoSleep(1);
12360         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12361     }
12362
12363     /* Kill off chess programs */
12364     if (first.pr != NoProc) {
12365         ExitAnalyzeMode();
12366
12367         DoSleep( appData.delayBeforeQuit );
12368         SendToProgram("quit\n", &first);
12369         DoSleep( appData.delayAfterQuit );
12370         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12371     }
12372     if (second.pr != NoProc) {
12373         DoSleep( appData.delayBeforeQuit );
12374         SendToProgram("quit\n", &second);
12375         DoSleep( appData.delayAfterQuit );
12376         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12377     }
12378     if (first.isr != NULL) {
12379         RemoveInputSource(first.isr);
12380     }
12381     if (second.isr != NULL) {
12382         RemoveInputSource(second.isr);
12383     }
12384
12385     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12386     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12387
12388     ShutDownFrontEnd();
12389     exit(status);
12390 }
12391
12392 void
12393 PauseEvent()
12394 {
12395     if (appData.debugMode)
12396         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12397     if (pausing) {
12398         pausing = FALSE;
12399         ModeHighlight();
12400         if (gameMode == MachinePlaysWhite ||
12401             gameMode == MachinePlaysBlack) {
12402             StartClocks();
12403         } else {
12404             DisplayBothClocks();
12405         }
12406         if (gameMode == PlayFromGameFile) {
12407             if (appData.timeDelay >= 0)
12408                 AutoPlayGameLoop();
12409         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12410             Reset(FALSE, TRUE);
12411             SendToICS(ics_prefix);
12412             SendToICS("refresh\n");
12413         } else if (currentMove < forwardMostMove) {
12414             ForwardInner(forwardMostMove);
12415         }
12416         pauseExamInvalid = FALSE;
12417     } else {
12418         switch (gameMode) {
12419           default:
12420             return;
12421           case IcsExamining:
12422             pauseExamForwardMostMove = forwardMostMove;
12423             pauseExamInvalid = FALSE;
12424             /* fall through */
12425           case IcsObserving:
12426           case IcsPlayingWhite:
12427           case IcsPlayingBlack:
12428             pausing = TRUE;
12429             ModeHighlight();
12430             return;
12431           case PlayFromGameFile:
12432             (void) StopLoadGameTimer();
12433             pausing = TRUE;
12434             ModeHighlight();
12435             break;
12436           case BeginningOfGame:
12437             if (appData.icsActive) return;
12438             /* else fall through */
12439           case MachinePlaysWhite:
12440           case MachinePlaysBlack:
12441           case TwoMachinesPlay:
12442             if (forwardMostMove == 0)
12443               return;           /* don't pause if no one has moved */
12444             if ((gameMode == MachinePlaysWhite &&
12445                  !WhiteOnMove(forwardMostMove)) ||
12446                 (gameMode == MachinePlaysBlack &&
12447                  WhiteOnMove(forwardMostMove))) {
12448                 StopClocks();
12449             }
12450             pausing = TRUE;
12451             ModeHighlight();
12452             break;
12453         }
12454     }
12455 }
12456
12457 void
12458 EditCommentEvent()
12459 {
12460     char title[MSG_SIZ];
12461
12462     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12463       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12464     } else {
12465       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12466                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12467                parseList[currentMove - 1]);
12468     }
12469
12470     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12471 }
12472
12473
12474 void
12475 EditTagsEvent()
12476 {
12477     char *tags = PGNTags(&gameInfo);
12478     bookUp = FALSE;
12479     EditTagsPopUp(tags, NULL);
12480     free(tags);
12481 }
12482
12483 void
12484 AnalyzeModeEvent()
12485 {
12486     if (appData.noChessProgram || gameMode == AnalyzeMode)
12487       return;
12488
12489     if (gameMode != AnalyzeFile) {
12490         if (!appData.icsEngineAnalyze) {
12491                EditGameEvent();
12492                if (gameMode != EditGame) return;
12493         }
12494         ResurrectChessProgram();
12495         SendToProgram("analyze\n", &first);
12496         first.analyzing = TRUE;
12497         /*first.maybeThinking = TRUE;*/
12498         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12499         EngineOutputPopUp();
12500     }
12501     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12502     pausing = FALSE;
12503     ModeHighlight();
12504     SetGameInfo();
12505
12506     StartAnalysisClock();
12507     GetTimeMark(&lastNodeCountTime);
12508     lastNodeCount = 0;
12509 }
12510
12511 void
12512 AnalyzeFileEvent()
12513 {
12514     if (appData.noChessProgram || gameMode == AnalyzeFile)
12515       return;
12516
12517     if (gameMode != AnalyzeMode) {
12518         EditGameEvent();
12519         if (gameMode != EditGame) return;
12520         ResurrectChessProgram();
12521         SendToProgram("analyze\n", &first);
12522         first.analyzing = TRUE;
12523         /*first.maybeThinking = TRUE;*/
12524         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12525         EngineOutputPopUp();
12526     }
12527     gameMode = AnalyzeFile;
12528     pausing = FALSE;
12529     ModeHighlight();
12530     SetGameInfo();
12531
12532     StartAnalysisClock();
12533     GetTimeMark(&lastNodeCountTime);
12534     lastNodeCount = 0;
12535 }
12536
12537 void
12538 MachineWhiteEvent()
12539 {
12540     char buf[MSG_SIZ];
12541     char *bookHit = NULL;
12542
12543     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12544       return;
12545
12546
12547     if (gameMode == PlayFromGameFile ||
12548         gameMode == TwoMachinesPlay  ||
12549         gameMode == Training         ||
12550         gameMode == AnalyzeMode      ||
12551         gameMode == EndOfGame)
12552         EditGameEvent();
12553
12554     if (gameMode == EditPosition)
12555         EditPositionDone(TRUE);
12556
12557     if (!WhiteOnMove(currentMove)) {
12558         DisplayError(_("It is not White's turn"), 0);
12559         return;
12560     }
12561
12562     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12563       ExitAnalyzeMode();
12564
12565     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12566         gameMode == AnalyzeFile)
12567         TruncateGame();
12568
12569     ResurrectChessProgram();    /* in case it isn't running */
12570     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12571         gameMode = MachinePlaysWhite;
12572         ResetClocks();
12573     } else
12574     gameMode = MachinePlaysWhite;
12575     pausing = FALSE;
12576     ModeHighlight();
12577     SetGameInfo();
12578     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12579     DisplayTitle(buf);
12580     if (first.sendName) {
12581       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12582       SendToProgram(buf, &first);
12583     }
12584     if (first.sendTime) {
12585       if (first.useColors) {
12586         SendToProgram("black\n", &first); /*gnu kludge*/
12587       }
12588       SendTimeRemaining(&first, TRUE);
12589     }
12590     if (first.useColors) {
12591       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12592     }
12593     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12594     SetMachineThinkingEnables();
12595     first.maybeThinking = TRUE;
12596     StartClocks();
12597     firstMove = FALSE;
12598
12599     if (appData.autoFlipView && !flipView) {
12600       flipView = !flipView;
12601       DrawPosition(FALSE, NULL);
12602       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12603     }
12604
12605     if(bookHit) { // [HGM] book: simulate book reply
12606         static char bookMove[MSG_SIZ]; // a bit generous?
12607
12608         programStats.nodes = programStats.depth = programStats.time =
12609         programStats.score = programStats.got_only_move = 0;
12610         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12611
12612         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12613         strcat(bookMove, bookHit);
12614         HandleMachineMove(bookMove, &first);
12615     }
12616 }
12617
12618 void
12619 MachineBlackEvent()
12620 {
12621   char buf[MSG_SIZ];
12622   char *bookHit = NULL;
12623
12624     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12625         return;
12626
12627
12628     if (gameMode == PlayFromGameFile ||
12629         gameMode == TwoMachinesPlay  ||
12630         gameMode == Training         ||
12631         gameMode == AnalyzeMode      ||
12632         gameMode == EndOfGame)
12633         EditGameEvent();
12634
12635     if (gameMode == EditPosition)
12636         EditPositionDone(TRUE);
12637
12638     if (WhiteOnMove(currentMove)) {
12639         DisplayError(_("It is not Black's turn"), 0);
12640         return;
12641     }
12642
12643     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12644       ExitAnalyzeMode();
12645
12646     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12647         gameMode == AnalyzeFile)
12648         TruncateGame();
12649
12650     ResurrectChessProgram();    /* in case it isn't running */
12651     gameMode = MachinePlaysBlack;
12652     pausing = FALSE;
12653     ModeHighlight();
12654     SetGameInfo();
12655     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12656     DisplayTitle(buf);
12657     if (first.sendName) {
12658       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12659       SendToProgram(buf, &first);
12660     }
12661     if (first.sendTime) {
12662       if (first.useColors) {
12663         SendToProgram("white\n", &first); /*gnu kludge*/
12664       }
12665       SendTimeRemaining(&first, FALSE);
12666     }
12667     if (first.useColors) {
12668       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12669     }
12670     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12671     SetMachineThinkingEnables();
12672     first.maybeThinking = TRUE;
12673     StartClocks();
12674
12675     if (appData.autoFlipView && flipView) {
12676       flipView = !flipView;
12677       DrawPosition(FALSE, NULL);
12678       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12679     }
12680     if(bookHit) { // [HGM] book: simulate book reply
12681         static char bookMove[MSG_SIZ]; // a bit generous?
12682
12683         programStats.nodes = programStats.depth = programStats.time =
12684         programStats.score = programStats.got_only_move = 0;
12685         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12686
12687         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12688         strcat(bookMove, bookHit);
12689         HandleMachineMove(bookMove, &first);
12690     }
12691 }
12692
12693
12694 void
12695 DisplayTwoMachinesTitle()
12696 {
12697     char buf[MSG_SIZ];
12698     if (appData.matchGames > 0) {
12699         if(appData.tourneyFile[0]) {
12700           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12701                    gameInfo.white, gameInfo.black,
12702                    nextGame+1, appData.matchGames+1,
12703                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12704         } else 
12705         if (first.twoMachinesColor[0] == 'w') {
12706           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12707                    gameInfo.white, gameInfo.black,
12708                    first.matchWins, second.matchWins,
12709                    matchGame - 1 - (first.matchWins + second.matchWins));
12710         } else {
12711           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12712                    gameInfo.white, gameInfo.black,
12713                    second.matchWins, first.matchWins,
12714                    matchGame - 1 - (first.matchWins + second.matchWins));
12715         }
12716     } else {
12717       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12718     }
12719     DisplayTitle(buf);
12720 }
12721
12722 void
12723 SettingsMenuIfReady()
12724 {
12725   if (second.lastPing != second.lastPong) {
12726     DisplayMessage("", _("Waiting for second chess program"));
12727     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12728     return;
12729   }
12730   ThawUI();
12731   DisplayMessage("", "");
12732   SettingsPopUp(&second);
12733 }
12734
12735 int
12736 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12737 {
12738     char buf[MSG_SIZ];
12739     if (cps->pr == NULL) {
12740         StartChessProgram(cps);
12741         if (cps->protocolVersion == 1) {
12742           retry();
12743         } else {
12744           /* kludge: allow timeout for initial "feature" command */
12745           FreezeUI();
12746           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12747           DisplayMessage("", buf);
12748           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12749         }
12750         return 1;
12751     }
12752     return 0;
12753 }
12754
12755 void
12756 TwoMachinesEvent P((void))
12757 {
12758     int i;
12759     char buf[MSG_SIZ];
12760     ChessProgramState *onmove;
12761     char *bookHit = NULL;
12762     static int stalling = 0;
12763     TimeMark now;
12764     long wait;
12765
12766     if (appData.noChessProgram) return;
12767
12768     switch (gameMode) {
12769       case TwoMachinesPlay:
12770         return;
12771       case MachinePlaysWhite:
12772       case MachinePlaysBlack:
12773         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12774             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12775             return;
12776         }
12777         /* fall through */
12778       case BeginningOfGame:
12779       case PlayFromGameFile:
12780       case EndOfGame:
12781         EditGameEvent();
12782         if (gameMode != EditGame) return;
12783         break;
12784       case EditPosition:
12785         EditPositionDone(TRUE);
12786         break;
12787       case AnalyzeMode:
12788       case AnalyzeFile:
12789         ExitAnalyzeMode();
12790         break;
12791       case EditGame:
12792       default:
12793         break;
12794     }
12795
12796 //    forwardMostMove = currentMove;
12797     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12798
12799     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12800
12801     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12802     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12803       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12804       return;
12805     }
12806     if(!stalling) {
12807       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12808       SendToProgram("force\n", &second);
12809       stalling = 1;
12810       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12811       return;
12812     }
12813     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12814     if(appData.matchPause>10000 || appData.matchPause<10)
12815                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12816     wait = SubtractTimeMarks(&now, &pauseStart);
12817     if(wait < appData.matchPause) {
12818         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12819         return;
12820     }
12821     stalling = 0;
12822     DisplayMessage("", "");
12823     if (startedFromSetupPosition) {
12824         SendBoard(&second, backwardMostMove);
12825     if (appData.debugMode) {
12826         fprintf(debugFP, "Two Machines\n");
12827     }
12828     }
12829     for (i = backwardMostMove; i < forwardMostMove; i++) {
12830         SendMoveToProgram(i, &second);
12831     }
12832
12833     gameMode = TwoMachinesPlay;
12834     pausing = FALSE;
12835     ModeHighlight();
12836     SetGameInfo();
12837     DisplayTwoMachinesTitle();
12838     firstMove = TRUE;
12839     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12840         onmove = &first;
12841     } else {
12842         onmove = &second;
12843     }
12844     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12845     SendToProgram(first.computerString, &first);
12846     if (first.sendName) {
12847       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12848       SendToProgram(buf, &first);
12849     }
12850     SendToProgram(second.computerString, &second);
12851     if (second.sendName) {
12852       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12853       SendToProgram(buf, &second);
12854     }
12855
12856     ResetClocks();
12857     if (!first.sendTime || !second.sendTime) {
12858         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12859         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12860     }
12861     if (onmove->sendTime) {
12862       if (onmove->useColors) {
12863         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12864       }
12865       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12866     }
12867     if (onmove->useColors) {
12868       SendToProgram(onmove->twoMachinesColor, onmove);
12869     }
12870     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12871 //    SendToProgram("go\n", onmove);
12872     onmove->maybeThinking = TRUE;
12873     SetMachineThinkingEnables();
12874
12875     StartClocks();
12876
12877     if(bookHit) { // [HGM] book: simulate book reply
12878         static char bookMove[MSG_SIZ]; // a bit generous?
12879
12880         programStats.nodes = programStats.depth = programStats.time =
12881         programStats.score = programStats.got_only_move = 0;
12882         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12883
12884         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12885         strcat(bookMove, bookHit);
12886         savedMessage = bookMove; // args for deferred call
12887         savedState = onmove;
12888         ScheduleDelayedEvent(DeferredBookMove, 1);
12889     }
12890 }
12891
12892 void
12893 TrainingEvent()
12894 {
12895     if (gameMode == Training) {
12896       SetTrainingModeOff();
12897       gameMode = PlayFromGameFile;
12898       DisplayMessage("", _("Training mode off"));
12899     } else {
12900       gameMode = Training;
12901       animateTraining = appData.animate;
12902
12903       /* make sure we are not already at the end of the game */
12904       if (currentMove < forwardMostMove) {
12905         SetTrainingModeOn();
12906         DisplayMessage("", _("Training mode on"));
12907       } else {
12908         gameMode = PlayFromGameFile;
12909         DisplayError(_("Already at end of game"), 0);
12910       }
12911     }
12912     ModeHighlight();
12913 }
12914
12915 void
12916 IcsClientEvent()
12917 {
12918     if (!appData.icsActive) return;
12919     switch (gameMode) {
12920       case IcsPlayingWhite:
12921       case IcsPlayingBlack:
12922       case IcsObserving:
12923       case IcsIdle:
12924       case BeginningOfGame:
12925       case IcsExamining:
12926         return;
12927
12928       case EditGame:
12929         break;
12930
12931       case EditPosition:
12932         EditPositionDone(TRUE);
12933         break;
12934
12935       case AnalyzeMode:
12936       case AnalyzeFile:
12937         ExitAnalyzeMode();
12938         break;
12939
12940       default:
12941         EditGameEvent();
12942         break;
12943     }
12944
12945     gameMode = IcsIdle;
12946     ModeHighlight();
12947     return;
12948 }
12949
12950
12951 void
12952 EditGameEvent()
12953 {
12954     int i;
12955
12956     switch (gameMode) {
12957       case Training:
12958         SetTrainingModeOff();
12959         break;
12960       case MachinePlaysWhite:
12961       case MachinePlaysBlack:
12962       case BeginningOfGame:
12963         SendToProgram("force\n", &first);
12964         SetUserThinkingEnables();
12965         break;
12966       case PlayFromGameFile:
12967         (void) StopLoadGameTimer();
12968         if (gameFileFP != NULL) {
12969             gameFileFP = NULL;
12970         }
12971         break;
12972       case EditPosition:
12973         EditPositionDone(TRUE);
12974         break;
12975       case AnalyzeMode:
12976       case AnalyzeFile:
12977         ExitAnalyzeMode();
12978         SendToProgram("force\n", &first);
12979         break;
12980       case TwoMachinesPlay:
12981         GameEnds(EndOfFile, NULL, GE_PLAYER);
12982         ResurrectChessProgram();
12983         SetUserThinkingEnables();
12984         break;
12985       case EndOfGame:
12986         ResurrectChessProgram();
12987         break;
12988       case IcsPlayingBlack:
12989       case IcsPlayingWhite:
12990         DisplayError(_("Warning: You are still playing a game"), 0);
12991         break;
12992       case IcsObserving:
12993         DisplayError(_("Warning: You are still observing a game"), 0);
12994         break;
12995       case IcsExamining:
12996         DisplayError(_("Warning: You are still examining a game"), 0);
12997         break;
12998       case IcsIdle:
12999         break;
13000       case EditGame:
13001       default:
13002         return;
13003     }
13004
13005     pausing = FALSE;
13006     StopClocks();
13007     first.offeredDraw = second.offeredDraw = 0;
13008
13009     if (gameMode == PlayFromGameFile) {
13010         whiteTimeRemaining = timeRemaining[0][currentMove];
13011         blackTimeRemaining = timeRemaining[1][currentMove];
13012         DisplayTitle("");
13013     }
13014
13015     if (gameMode == MachinePlaysWhite ||
13016         gameMode == MachinePlaysBlack ||
13017         gameMode == TwoMachinesPlay ||
13018         gameMode == EndOfGame) {
13019         i = forwardMostMove;
13020         while (i > currentMove) {
13021             SendToProgram("undo\n", &first);
13022             i--;
13023         }
13024         whiteTimeRemaining = timeRemaining[0][currentMove];
13025         blackTimeRemaining = timeRemaining[1][currentMove];
13026         DisplayBothClocks();
13027         if (whiteFlag || blackFlag) {
13028             whiteFlag = blackFlag = 0;
13029         }
13030         DisplayTitle("");
13031     }
13032
13033     gameMode = EditGame;
13034     ModeHighlight();
13035     SetGameInfo();
13036 }
13037
13038
13039 void
13040 EditPositionEvent()
13041 {
13042     if (gameMode == EditPosition) {
13043         EditGameEvent();
13044         return;
13045     }
13046
13047     EditGameEvent();
13048     if (gameMode != EditGame) return;
13049
13050     gameMode = EditPosition;
13051     ModeHighlight();
13052     SetGameInfo();
13053     if (currentMove > 0)
13054       CopyBoard(boards[0], boards[currentMove]);
13055
13056     blackPlaysFirst = !WhiteOnMove(currentMove);
13057     ResetClocks();
13058     currentMove = forwardMostMove = backwardMostMove = 0;
13059     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13060     DisplayMove(-1);
13061 }
13062
13063 void
13064 ExitAnalyzeMode()
13065 {
13066     /* [DM] icsEngineAnalyze - possible call from other functions */
13067     if (appData.icsEngineAnalyze) {
13068         appData.icsEngineAnalyze = FALSE;
13069
13070         DisplayMessage("",_("Close ICS engine analyze..."));
13071     }
13072     if (first.analysisSupport && first.analyzing) {
13073       SendToProgram("exit\n", &first);
13074       first.analyzing = FALSE;
13075     }
13076     thinkOutput[0] = NULLCHAR;
13077 }
13078
13079 void
13080 EditPositionDone(Boolean fakeRights)
13081 {
13082     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13083
13084     startedFromSetupPosition = TRUE;
13085     InitChessProgram(&first, FALSE);
13086     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13087       boards[0][EP_STATUS] = EP_NONE;
13088       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13089     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13090         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13091         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13092       } else boards[0][CASTLING][2] = NoRights;
13093     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13094         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13095         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13096       } else boards[0][CASTLING][5] = NoRights;
13097     }
13098     SendToProgram("force\n", &first);
13099     if (blackPlaysFirst) {
13100         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13101         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13102         currentMove = forwardMostMove = backwardMostMove = 1;
13103         CopyBoard(boards[1], boards[0]);
13104     } else {
13105         currentMove = forwardMostMove = backwardMostMove = 0;
13106     }
13107     SendBoard(&first, forwardMostMove);
13108     if (appData.debugMode) {
13109         fprintf(debugFP, "EditPosDone\n");
13110     }
13111     DisplayTitle("");
13112     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13113     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13114     gameMode = EditGame;
13115     ModeHighlight();
13116     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13117     ClearHighlights(); /* [AS] */
13118 }
13119
13120 /* Pause for `ms' milliseconds */
13121 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13122 void
13123 TimeDelay(ms)
13124      long ms;
13125 {
13126     TimeMark m1, m2;
13127
13128     GetTimeMark(&m1);
13129     do {
13130         GetTimeMark(&m2);
13131     } while (SubtractTimeMarks(&m2, &m1) < ms);
13132 }
13133
13134 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13135 void
13136 SendMultiLineToICS(buf)
13137      char *buf;
13138 {
13139     char temp[MSG_SIZ+1], *p;
13140     int len;
13141
13142     len = strlen(buf);
13143     if (len > MSG_SIZ)
13144       len = MSG_SIZ;
13145
13146     strncpy(temp, buf, len);
13147     temp[len] = 0;
13148
13149     p = temp;
13150     while (*p) {
13151         if (*p == '\n' || *p == '\r')
13152           *p = ' ';
13153         ++p;
13154     }
13155
13156     strcat(temp, "\n");
13157     SendToICS(temp);
13158     SendToPlayer(temp, strlen(temp));
13159 }
13160
13161 void
13162 SetWhiteToPlayEvent()
13163 {
13164     if (gameMode == EditPosition) {
13165         blackPlaysFirst = FALSE;
13166         DisplayBothClocks();    /* works because currentMove is 0 */
13167     } else if (gameMode == IcsExamining) {
13168         SendToICS(ics_prefix);
13169         SendToICS("tomove white\n");
13170     }
13171 }
13172
13173 void
13174 SetBlackToPlayEvent()
13175 {
13176     if (gameMode == EditPosition) {
13177         blackPlaysFirst = TRUE;
13178         currentMove = 1;        /* kludge */
13179         DisplayBothClocks();
13180         currentMove = 0;
13181     } else if (gameMode == IcsExamining) {
13182         SendToICS(ics_prefix);
13183         SendToICS("tomove black\n");
13184     }
13185 }
13186
13187 void
13188 EditPositionMenuEvent(selection, x, y)
13189      ChessSquare selection;
13190      int x, y;
13191 {
13192     char buf[MSG_SIZ];
13193     ChessSquare piece = boards[0][y][x];
13194
13195     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13196
13197     switch (selection) {
13198       case ClearBoard:
13199         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13200             SendToICS(ics_prefix);
13201             SendToICS("bsetup clear\n");
13202         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13203             SendToICS(ics_prefix);
13204             SendToICS("clearboard\n");
13205         } else {
13206             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13207                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13208                 for (y = 0; y < BOARD_HEIGHT; y++) {
13209                     if (gameMode == IcsExamining) {
13210                         if (boards[currentMove][y][x] != EmptySquare) {
13211                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13212                                     AAA + x, ONE + y);
13213                             SendToICS(buf);
13214                         }
13215                     } else {
13216                         boards[0][y][x] = p;
13217                     }
13218                 }
13219             }
13220         }
13221         if (gameMode == EditPosition) {
13222             DrawPosition(FALSE, boards[0]);
13223         }
13224         break;
13225
13226       case WhitePlay:
13227         SetWhiteToPlayEvent();
13228         break;
13229
13230       case BlackPlay:
13231         SetBlackToPlayEvent();
13232         break;
13233
13234       case EmptySquare:
13235         if (gameMode == IcsExamining) {
13236             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13237             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13238             SendToICS(buf);
13239         } else {
13240             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13241                 if(x == BOARD_LEFT-2) {
13242                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13243                     boards[0][y][1] = 0;
13244                 } else
13245                 if(x == BOARD_RGHT+1) {
13246                     if(y >= gameInfo.holdingsSize) break;
13247                     boards[0][y][BOARD_WIDTH-2] = 0;
13248                 } else break;
13249             }
13250             boards[0][y][x] = EmptySquare;
13251             DrawPosition(FALSE, boards[0]);
13252         }
13253         break;
13254
13255       case PromotePiece:
13256         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13257            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13258             selection = (ChessSquare) (PROMOTED piece);
13259         } else if(piece == EmptySquare) selection = WhiteSilver;
13260         else selection = (ChessSquare)((int)piece - 1);
13261         goto defaultlabel;
13262
13263       case DemotePiece:
13264         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13265            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13266             selection = (ChessSquare) (DEMOTED piece);
13267         } else if(piece == EmptySquare) selection = BlackSilver;
13268         else selection = (ChessSquare)((int)piece + 1);
13269         goto defaultlabel;
13270
13271       case WhiteQueen:
13272       case BlackQueen:
13273         if(gameInfo.variant == VariantShatranj ||
13274            gameInfo.variant == VariantXiangqi  ||
13275            gameInfo.variant == VariantCourier  ||
13276            gameInfo.variant == VariantMakruk     )
13277             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13278         goto defaultlabel;
13279
13280       case WhiteKing:
13281       case BlackKing:
13282         if(gameInfo.variant == VariantXiangqi)
13283             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13284         if(gameInfo.variant == VariantKnightmate)
13285             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13286       default:
13287         defaultlabel:
13288         if (gameMode == IcsExamining) {
13289             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13290             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13291                      PieceToChar(selection), AAA + x, ONE + y);
13292             SendToICS(buf);
13293         } else {
13294             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13295                 int n;
13296                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13297                     n = PieceToNumber(selection - BlackPawn);
13298                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13299                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13300                     boards[0][BOARD_HEIGHT-1-n][1]++;
13301                 } else
13302                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13303                     n = PieceToNumber(selection);
13304                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13305                     boards[0][n][BOARD_WIDTH-1] = selection;
13306                     boards[0][n][BOARD_WIDTH-2]++;
13307                 }
13308             } else
13309             boards[0][y][x] = selection;
13310             DrawPosition(TRUE, boards[0]);
13311         }
13312         break;
13313     }
13314 }
13315
13316
13317 void
13318 DropMenuEvent(selection, x, y)
13319      ChessSquare selection;
13320      int x, y;
13321 {
13322     ChessMove moveType;
13323
13324     switch (gameMode) {
13325       case IcsPlayingWhite:
13326       case MachinePlaysBlack:
13327         if (!WhiteOnMove(currentMove)) {
13328             DisplayMoveError(_("It is Black's turn"));
13329             return;
13330         }
13331         moveType = WhiteDrop;
13332         break;
13333       case IcsPlayingBlack:
13334       case MachinePlaysWhite:
13335         if (WhiteOnMove(currentMove)) {
13336             DisplayMoveError(_("It is White's turn"));
13337             return;
13338         }
13339         moveType = BlackDrop;
13340         break;
13341       case EditGame:
13342         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13343         break;
13344       default:
13345         return;
13346     }
13347
13348     if (moveType == BlackDrop && selection < BlackPawn) {
13349       selection = (ChessSquare) ((int) selection
13350                                  + (int) BlackPawn - (int) WhitePawn);
13351     }
13352     if (boards[currentMove][y][x] != EmptySquare) {
13353         DisplayMoveError(_("That square is occupied"));
13354         return;
13355     }
13356
13357     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13358 }
13359
13360 void
13361 AcceptEvent()
13362 {
13363     /* Accept a pending offer of any kind from opponent */
13364
13365     if (appData.icsActive) {
13366         SendToICS(ics_prefix);
13367         SendToICS("accept\n");
13368     } else if (cmailMsgLoaded) {
13369         if (currentMove == cmailOldMove &&
13370             commentList[cmailOldMove] != NULL &&
13371             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13372                    "Black offers a draw" : "White offers a draw")) {
13373             TruncateGame();
13374             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13375             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13376         } else {
13377             DisplayError(_("There is no pending offer on this move"), 0);
13378             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13379         }
13380     } else {
13381         /* Not used for offers from chess program */
13382     }
13383 }
13384
13385 void
13386 DeclineEvent()
13387 {
13388     /* Decline a pending offer of any kind from opponent */
13389
13390     if (appData.icsActive) {
13391         SendToICS(ics_prefix);
13392         SendToICS("decline\n");
13393     } else if (cmailMsgLoaded) {
13394         if (currentMove == cmailOldMove &&
13395             commentList[cmailOldMove] != NULL &&
13396             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13397                    "Black offers a draw" : "White offers a draw")) {
13398 #ifdef NOTDEF
13399             AppendComment(cmailOldMove, "Draw declined", TRUE);
13400             DisplayComment(cmailOldMove - 1, "Draw declined");
13401 #endif /*NOTDEF*/
13402         } else {
13403             DisplayError(_("There is no pending offer on this move"), 0);
13404         }
13405     } else {
13406         /* Not used for offers from chess program */
13407     }
13408 }
13409
13410 void
13411 RematchEvent()
13412 {
13413     /* Issue ICS rematch command */
13414     if (appData.icsActive) {
13415         SendToICS(ics_prefix);
13416         SendToICS("rematch\n");
13417     }
13418 }
13419
13420 void
13421 CallFlagEvent()
13422 {
13423     /* Call your opponent's flag (claim a win on time) */
13424     if (appData.icsActive) {
13425         SendToICS(ics_prefix);
13426         SendToICS("flag\n");
13427     } else {
13428         switch (gameMode) {
13429           default:
13430             return;
13431           case MachinePlaysWhite:
13432             if (whiteFlag) {
13433                 if (blackFlag)
13434                   GameEnds(GameIsDrawn, "Both players ran out of time",
13435                            GE_PLAYER);
13436                 else
13437                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13438             } else {
13439                 DisplayError(_("Your opponent is not out of time"), 0);
13440             }
13441             break;
13442           case MachinePlaysBlack:
13443             if (blackFlag) {
13444                 if (whiteFlag)
13445                   GameEnds(GameIsDrawn, "Both players ran out of time",
13446                            GE_PLAYER);
13447                 else
13448                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13449             } else {
13450                 DisplayError(_("Your opponent is not out of time"), 0);
13451             }
13452             break;
13453         }
13454     }
13455 }
13456
13457 void
13458 ClockClick(int which)
13459 {       // [HGM] code moved to back-end from winboard.c
13460         if(which) { // black clock
13461           if (gameMode == EditPosition || gameMode == IcsExamining) {
13462             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13463             SetBlackToPlayEvent();
13464           } else if (gameMode == EditGame || shiftKey) {
13465             AdjustClock(which, -1);
13466           } else if (gameMode == IcsPlayingWhite ||
13467                      gameMode == MachinePlaysBlack) {
13468             CallFlagEvent();
13469           }
13470         } else { // white clock
13471           if (gameMode == EditPosition || gameMode == IcsExamining) {
13472             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13473             SetWhiteToPlayEvent();
13474           } else if (gameMode == EditGame || shiftKey) {
13475             AdjustClock(which, -1);
13476           } else if (gameMode == IcsPlayingBlack ||
13477                    gameMode == MachinePlaysWhite) {
13478             CallFlagEvent();
13479           }
13480         }
13481 }
13482
13483 void
13484 DrawEvent()
13485 {
13486     /* Offer draw or accept pending draw offer from opponent */
13487
13488     if (appData.icsActive) {
13489         /* Note: tournament rules require draw offers to be
13490            made after you make your move but before you punch
13491            your clock.  Currently ICS doesn't let you do that;
13492            instead, you immediately punch your clock after making
13493            a move, but you can offer a draw at any time. */
13494
13495         SendToICS(ics_prefix);
13496         SendToICS("draw\n");
13497         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13498     } else if (cmailMsgLoaded) {
13499         if (currentMove == cmailOldMove &&
13500             commentList[cmailOldMove] != NULL &&
13501             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13502                    "Black offers a draw" : "White offers a draw")) {
13503             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13504             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13505         } else if (currentMove == cmailOldMove + 1) {
13506             char *offer = WhiteOnMove(cmailOldMove) ?
13507               "White offers a draw" : "Black offers a draw";
13508             AppendComment(currentMove, offer, TRUE);
13509             DisplayComment(currentMove - 1, offer);
13510             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13511         } else {
13512             DisplayError(_("You must make your move before offering a draw"), 0);
13513             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13514         }
13515     } else if (first.offeredDraw) {
13516         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13517     } else {
13518         if (first.sendDrawOffers) {
13519             SendToProgram("draw\n", &first);
13520             userOfferedDraw = TRUE;
13521         }
13522     }
13523 }
13524
13525 void
13526 AdjournEvent()
13527 {
13528     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13529
13530     if (appData.icsActive) {
13531         SendToICS(ics_prefix);
13532         SendToICS("adjourn\n");
13533     } else {
13534         /* Currently GNU Chess doesn't offer or accept Adjourns */
13535     }
13536 }
13537
13538
13539 void
13540 AbortEvent()
13541 {
13542     /* Offer Abort or accept pending Abort offer from opponent */
13543
13544     if (appData.icsActive) {
13545         SendToICS(ics_prefix);
13546         SendToICS("abort\n");
13547     } else {
13548         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13549     }
13550 }
13551
13552 void
13553 ResignEvent()
13554 {
13555     /* Resign.  You can do this even if it's not your turn. */
13556
13557     if (appData.icsActive) {
13558         SendToICS(ics_prefix);
13559         SendToICS("resign\n");
13560     } else {
13561         switch (gameMode) {
13562           case MachinePlaysWhite:
13563             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13564             break;
13565           case MachinePlaysBlack:
13566             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13567             break;
13568           case EditGame:
13569             if (cmailMsgLoaded) {
13570                 TruncateGame();
13571                 if (WhiteOnMove(cmailOldMove)) {
13572                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13573                 } else {
13574                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13575                 }
13576                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13577             }
13578             break;
13579           default:
13580             break;
13581         }
13582     }
13583 }
13584
13585
13586 void
13587 StopObservingEvent()
13588 {
13589     /* Stop observing current games */
13590     SendToICS(ics_prefix);
13591     SendToICS("unobserve\n");
13592 }
13593
13594 void
13595 StopExaminingEvent()
13596 {
13597     /* Stop observing current game */
13598     SendToICS(ics_prefix);
13599     SendToICS("unexamine\n");
13600 }
13601
13602 void
13603 ForwardInner(target)
13604      int target;
13605 {
13606     int limit;
13607
13608     if (appData.debugMode)
13609         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13610                 target, currentMove, forwardMostMove);
13611
13612     if (gameMode == EditPosition)
13613       return;
13614
13615     if (gameMode == PlayFromGameFile && !pausing)
13616       PauseEvent();
13617
13618     if (gameMode == IcsExamining && pausing)
13619       limit = pauseExamForwardMostMove;
13620     else
13621       limit = forwardMostMove;
13622
13623     if (target > limit) target = limit;
13624
13625     if (target > 0 && moveList[target - 1][0]) {
13626         int fromX, fromY, toX, toY;
13627         toX = moveList[target - 1][2] - AAA;
13628         toY = moveList[target - 1][3] - ONE;
13629         if (moveList[target - 1][1] == '@') {
13630             if (appData.highlightLastMove) {
13631                 SetHighlights(-1, -1, toX, toY);
13632             }
13633         } else {
13634             fromX = moveList[target - 1][0] - AAA;
13635             fromY = moveList[target - 1][1] - ONE;
13636             if (target == currentMove + 1) {
13637                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13638             }
13639             if (appData.highlightLastMove) {
13640                 SetHighlights(fromX, fromY, toX, toY);
13641             }
13642         }
13643     }
13644     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13645         gameMode == Training || gameMode == PlayFromGameFile ||
13646         gameMode == AnalyzeFile) {
13647         while (currentMove < target) {
13648             SendMoveToProgram(currentMove++, &first);
13649         }
13650     } else {
13651         currentMove = target;
13652     }
13653
13654     if (gameMode == EditGame || gameMode == EndOfGame) {
13655         whiteTimeRemaining = timeRemaining[0][currentMove];
13656         blackTimeRemaining = timeRemaining[1][currentMove];
13657     }
13658     DisplayBothClocks();
13659     DisplayMove(currentMove - 1);
13660     DrawPosition(FALSE, boards[currentMove]);
13661     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13662     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13663         DisplayComment(currentMove - 1, commentList[currentMove]);
13664     }
13665     DisplayBook(currentMove);
13666 }
13667
13668
13669 void
13670 ForwardEvent()
13671 {
13672     if (gameMode == IcsExamining && !pausing) {
13673         SendToICS(ics_prefix);
13674         SendToICS("forward\n");
13675     } else {
13676         ForwardInner(currentMove + 1);
13677     }
13678 }
13679
13680 void
13681 ToEndEvent()
13682 {
13683     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13684         /* to optimze, we temporarily turn off analysis mode while we feed
13685          * the remaining moves to the engine. Otherwise we get analysis output
13686          * after each move.
13687          */
13688         if (first.analysisSupport) {
13689           SendToProgram("exit\nforce\n", &first);
13690           first.analyzing = FALSE;
13691         }
13692     }
13693
13694     if (gameMode == IcsExamining && !pausing) {
13695         SendToICS(ics_prefix);
13696         SendToICS("forward 999999\n");
13697     } else {
13698         ForwardInner(forwardMostMove);
13699     }
13700
13701     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13702         /* we have fed all the moves, so reactivate analysis mode */
13703         SendToProgram("analyze\n", &first);
13704         first.analyzing = TRUE;
13705         /*first.maybeThinking = TRUE;*/
13706         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13707     }
13708 }
13709
13710 void
13711 BackwardInner(target)
13712      int target;
13713 {
13714     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13715
13716     if (appData.debugMode)
13717         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13718                 target, currentMove, forwardMostMove);
13719
13720     if (gameMode == EditPosition) return;
13721     if (currentMove <= backwardMostMove) {
13722         ClearHighlights();
13723         DrawPosition(full_redraw, boards[currentMove]);
13724         return;
13725     }
13726     if (gameMode == PlayFromGameFile && !pausing)
13727       PauseEvent();
13728
13729     if (moveList[target][0]) {
13730         int fromX, fromY, toX, toY;
13731         toX = moveList[target][2] - AAA;
13732         toY = moveList[target][3] - ONE;
13733         if (moveList[target][1] == '@') {
13734             if (appData.highlightLastMove) {
13735                 SetHighlights(-1, -1, toX, toY);
13736             }
13737         } else {
13738             fromX = moveList[target][0] - AAA;
13739             fromY = moveList[target][1] - ONE;
13740             if (target == currentMove - 1) {
13741                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13742             }
13743             if (appData.highlightLastMove) {
13744                 SetHighlights(fromX, fromY, toX, toY);
13745             }
13746         }
13747     }
13748     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13749         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13750         while (currentMove > target) {
13751             SendToProgram("undo\n", &first);
13752             currentMove--;
13753         }
13754     } else {
13755         currentMove = target;
13756     }
13757
13758     if (gameMode == EditGame || gameMode == EndOfGame) {
13759         whiteTimeRemaining = timeRemaining[0][currentMove];
13760         blackTimeRemaining = timeRemaining[1][currentMove];
13761     }
13762     DisplayBothClocks();
13763     DisplayMove(currentMove - 1);
13764     DrawPosition(full_redraw, boards[currentMove]);
13765     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13766     // [HGM] PV info: routine tests if comment empty
13767     DisplayComment(currentMove - 1, commentList[currentMove]);
13768     DisplayBook(currentMove);
13769 }
13770
13771 void
13772 BackwardEvent()
13773 {
13774     if (gameMode == IcsExamining && !pausing) {
13775         SendToICS(ics_prefix);
13776         SendToICS("backward\n");
13777     } else {
13778         BackwardInner(currentMove - 1);
13779     }
13780 }
13781
13782 void
13783 ToStartEvent()
13784 {
13785     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13786         /* to optimize, we temporarily turn off analysis mode while we undo
13787          * all the moves. Otherwise we get analysis output after each undo.
13788          */
13789         if (first.analysisSupport) {
13790           SendToProgram("exit\nforce\n", &first);
13791           first.analyzing = FALSE;
13792         }
13793     }
13794
13795     if (gameMode == IcsExamining && !pausing) {
13796         SendToICS(ics_prefix);
13797         SendToICS("backward 999999\n");
13798     } else {
13799         BackwardInner(backwardMostMove);
13800     }
13801
13802     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13803         /* we have fed all the moves, so reactivate analysis mode */
13804         SendToProgram("analyze\n", &first);
13805         first.analyzing = TRUE;
13806         /*first.maybeThinking = TRUE;*/
13807         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13808     }
13809 }
13810
13811 void
13812 ToNrEvent(int to)
13813 {
13814   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13815   if (to >= forwardMostMove) to = forwardMostMove;
13816   if (to <= backwardMostMove) to = backwardMostMove;
13817   if (to < currentMove) {
13818     BackwardInner(to);
13819   } else {
13820     ForwardInner(to);
13821   }
13822 }
13823
13824 void
13825 RevertEvent(Boolean annotate)
13826 {
13827     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13828         return;
13829     }
13830     if (gameMode != IcsExamining) {
13831         DisplayError(_("You are not examining a game"), 0);
13832         return;
13833     }
13834     if (pausing) {
13835         DisplayError(_("You can't revert while pausing"), 0);
13836         return;
13837     }
13838     SendToICS(ics_prefix);
13839     SendToICS("revert\n");
13840 }
13841
13842 void
13843 RetractMoveEvent()
13844 {
13845     switch (gameMode) {
13846       case MachinePlaysWhite:
13847       case MachinePlaysBlack:
13848         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13849             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13850             return;
13851         }
13852         if (forwardMostMove < 2) return;
13853         currentMove = forwardMostMove = forwardMostMove - 2;
13854         whiteTimeRemaining = timeRemaining[0][currentMove];
13855         blackTimeRemaining = timeRemaining[1][currentMove];
13856         DisplayBothClocks();
13857         DisplayMove(currentMove - 1);
13858         ClearHighlights();/*!! could figure this out*/
13859         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13860         SendToProgram("remove\n", &first);
13861         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13862         break;
13863
13864       case BeginningOfGame:
13865       default:
13866         break;
13867
13868       case IcsPlayingWhite:
13869       case IcsPlayingBlack:
13870         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13871             SendToICS(ics_prefix);
13872             SendToICS("takeback 2\n");
13873         } else {
13874             SendToICS(ics_prefix);
13875             SendToICS("takeback 1\n");
13876         }
13877         break;
13878     }
13879 }
13880
13881 void
13882 MoveNowEvent()
13883 {
13884     ChessProgramState *cps;
13885
13886     switch (gameMode) {
13887       case MachinePlaysWhite:
13888         if (!WhiteOnMove(forwardMostMove)) {
13889             DisplayError(_("It is your turn"), 0);
13890             return;
13891         }
13892         cps = &first;
13893         break;
13894       case MachinePlaysBlack:
13895         if (WhiteOnMove(forwardMostMove)) {
13896             DisplayError(_("It is your turn"), 0);
13897             return;
13898         }
13899         cps = &first;
13900         break;
13901       case TwoMachinesPlay:
13902         if (WhiteOnMove(forwardMostMove) ==
13903             (first.twoMachinesColor[0] == 'w')) {
13904             cps = &first;
13905         } else {
13906             cps = &second;
13907         }
13908         break;
13909       case BeginningOfGame:
13910       default:
13911         return;
13912     }
13913     SendToProgram("?\n", cps);
13914 }
13915
13916 void
13917 TruncateGameEvent()
13918 {
13919     EditGameEvent();
13920     if (gameMode != EditGame) return;
13921     TruncateGame();
13922 }
13923
13924 void
13925 TruncateGame()
13926 {
13927     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13928     if (forwardMostMove > currentMove) {
13929         if (gameInfo.resultDetails != NULL) {
13930             free(gameInfo.resultDetails);
13931             gameInfo.resultDetails = NULL;
13932             gameInfo.result = GameUnfinished;
13933         }
13934         forwardMostMove = currentMove;
13935         HistorySet(parseList, backwardMostMove, forwardMostMove,
13936                    currentMove-1);
13937     }
13938 }
13939
13940 void
13941 HintEvent()
13942 {
13943     if (appData.noChessProgram) return;
13944     switch (gameMode) {
13945       case MachinePlaysWhite:
13946         if (WhiteOnMove(forwardMostMove)) {
13947             DisplayError(_("Wait until your turn"), 0);
13948             return;
13949         }
13950         break;
13951       case BeginningOfGame:
13952       case MachinePlaysBlack:
13953         if (!WhiteOnMove(forwardMostMove)) {
13954             DisplayError(_("Wait until your turn"), 0);
13955             return;
13956         }
13957         break;
13958       default:
13959         DisplayError(_("No hint available"), 0);
13960         return;
13961     }
13962     SendToProgram("hint\n", &first);
13963     hintRequested = TRUE;
13964 }
13965
13966 void
13967 BookEvent()
13968 {
13969     if (appData.noChessProgram) return;
13970     switch (gameMode) {
13971       case MachinePlaysWhite:
13972         if (WhiteOnMove(forwardMostMove)) {
13973             DisplayError(_("Wait until your turn"), 0);
13974             return;
13975         }
13976         break;
13977       case BeginningOfGame:
13978       case MachinePlaysBlack:
13979         if (!WhiteOnMove(forwardMostMove)) {
13980             DisplayError(_("Wait until your turn"), 0);
13981             return;
13982         }
13983         break;
13984       case EditPosition:
13985         EditPositionDone(TRUE);
13986         break;
13987       case TwoMachinesPlay:
13988         return;
13989       default:
13990         break;
13991     }
13992     SendToProgram("bk\n", &first);
13993     bookOutput[0] = NULLCHAR;
13994     bookRequested = TRUE;
13995 }
13996
13997 void
13998 AboutGameEvent()
13999 {
14000     char *tags = PGNTags(&gameInfo);
14001     TagsPopUp(tags, CmailMsg());
14002     free(tags);
14003 }
14004
14005 /* end button procedures */
14006
14007 void
14008 PrintPosition(fp, move)
14009      FILE *fp;
14010      int move;
14011 {
14012     int i, j;
14013
14014     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14015         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14016             char c = PieceToChar(boards[move][i][j]);
14017             fputc(c == 'x' ? '.' : c, fp);
14018             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14019         }
14020     }
14021     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14022       fprintf(fp, "white to play\n");
14023     else
14024       fprintf(fp, "black to play\n");
14025 }
14026
14027 void
14028 PrintOpponents(fp)
14029      FILE *fp;
14030 {
14031     if (gameInfo.white != NULL) {
14032         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14033     } else {
14034         fprintf(fp, "\n");
14035     }
14036 }
14037
14038 /* Find last component of program's own name, using some heuristics */
14039 void
14040 TidyProgramName(prog, host, buf)
14041      char *prog, *host, buf[MSG_SIZ];
14042 {
14043     char *p, *q;
14044     int local = (strcmp(host, "localhost") == 0);
14045     while (!local && (p = strchr(prog, ';')) != NULL) {
14046         p++;
14047         while (*p == ' ') p++;
14048         prog = p;
14049     }
14050     if (*prog == '"' || *prog == '\'') {
14051         q = strchr(prog + 1, *prog);
14052     } else {
14053         q = strchr(prog, ' ');
14054     }
14055     if (q == NULL) q = prog + strlen(prog);
14056     p = q;
14057     while (p >= prog && *p != '/' && *p != '\\') p--;
14058     p++;
14059     if(p == prog && *p == '"') p++;
14060     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14061     memcpy(buf, p, q - p);
14062     buf[q - p] = NULLCHAR;
14063     if (!local) {
14064         strcat(buf, "@");
14065         strcat(buf, host);
14066     }
14067 }
14068
14069 char *
14070 TimeControlTagValue()
14071 {
14072     char buf[MSG_SIZ];
14073     if (!appData.clockMode) {
14074       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14075     } else if (movesPerSession > 0) {
14076       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14077     } else if (timeIncrement == 0) {
14078       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14079     } else {
14080       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14081     }
14082     return StrSave(buf);
14083 }
14084
14085 void
14086 SetGameInfo()
14087 {
14088     /* This routine is used only for certain modes */
14089     VariantClass v = gameInfo.variant;
14090     ChessMove r = GameUnfinished;
14091     char *p = NULL;
14092
14093     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14094         r = gameInfo.result;
14095         p = gameInfo.resultDetails;
14096         gameInfo.resultDetails = NULL;
14097     }
14098     ClearGameInfo(&gameInfo);
14099     gameInfo.variant = v;
14100
14101     switch (gameMode) {
14102       case MachinePlaysWhite:
14103         gameInfo.event = StrSave( appData.pgnEventHeader );
14104         gameInfo.site = StrSave(HostName());
14105         gameInfo.date = PGNDate();
14106         gameInfo.round = StrSave("-");
14107         gameInfo.white = StrSave(first.tidy);
14108         gameInfo.black = StrSave(UserName());
14109         gameInfo.timeControl = TimeControlTagValue();
14110         break;
14111
14112       case MachinePlaysBlack:
14113         gameInfo.event = StrSave( appData.pgnEventHeader );
14114         gameInfo.site = StrSave(HostName());
14115         gameInfo.date = PGNDate();
14116         gameInfo.round = StrSave("-");
14117         gameInfo.white = StrSave(UserName());
14118         gameInfo.black = StrSave(first.tidy);
14119         gameInfo.timeControl = TimeControlTagValue();
14120         break;
14121
14122       case TwoMachinesPlay:
14123         gameInfo.event = StrSave( appData.pgnEventHeader );
14124         gameInfo.site = StrSave(HostName());
14125         gameInfo.date = PGNDate();
14126         if (roundNr > 0) {
14127             char buf[MSG_SIZ];
14128             snprintf(buf, MSG_SIZ, "%d", roundNr);
14129             gameInfo.round = StrSave(buf);
14130         } else {
14131             gameInfo.round = StrSave("-");
14132         }
14133         if (first.twoMachinesColor[0] == 'w') {
14134             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14135             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14136         } else {
14137             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14138             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14139         }
14140         gameInfo.timeControl = TimeControlTagValue();
14141         break;
14142
14143       case EditGame:
14144         gameInfo.event = StrSave("Edited game");
14145         gameInfo.site = StrSave(HostName());
14146         gameInfo.date = PGNDate();
14147         gameInfo.round = StrSave("-");
14148         gameInfo.white = StrSave("-");
14149         gameInfo.black = StrSave("-");
14150         gameInfo.result = r;
14151         gameInfo.resultDetails = p;
14152         break;
14153
14154       case EditPosition:
14155         gameInfo.event = StrSave("Edited position");
14156         gameInfo.site = StrSave(HostName());
14157         gameInfo.date = PGNDate();
14158         gameInfo.round = StrSave("-");
14159         gameInfo.white = StrSave("-");
14160         gameInfo.black = StrSave("-");
14161         break;
14162
14163       case IcsPlayingWhite:
14164       case IcsPlayingBlack:
14165       case IcsObserving:
14166       case IcsExamining:
14167         break;
14168
14169       case PlayFromGameFile:
14170         gameInfo.event = StrSave("Game from non-PGN file");
14171         gameInfo.site = StrSave(HostName());
14172         gameInfo.date = PGNDate();
14173         gameInfo.round = StrSave("-");
14174         gameInfo.white = StrSave("?");
14175         gameInfo.black = StrSave("?");
14176         break;
14177
14178       default:
14179         break;
14180     }
14181 }
14182
14183 void
14184 ReplaceComment(index, text)
14185      int index;
14186      char *text;
14187 {
14188     int len;
14189     char *p;
14190     float score;
14191
14192     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14193        pvInfoList[index-1].depth == len &&
14194        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14195        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14196     while (*text == '\n') text++;
14197     len = strlen(text);
14198     while (len > 0 && text[len - 1] == '\n') len--;
14199
14200     if (commentList[index] != NULL)
14201       free(commentList[index]);
14202
14203     if (len == 0) {
14204         commentList[index] = NULL;
14205         return;
14206     }
14207   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14208       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14209       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14210     commentList[index] = (char *) malloc(len + 2);
14211     strncpy(commentList[index], text, len);
14212     commentList[index][len] = '\n';
14213     commentList[index][len + 1] = NULLCHAR;
14214   } else {
14215     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14216     char *p;
14217     commentList[index] = (char *) malloc(len + 7);
14218     safeStrCpy(commentList[index], "{\n", 3);
14219     safeStrCpy(commentList[index]+2, text, len+1);
14220     commentList[index][len+2] = NULLCHAR;
14221     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14222     strcat(commentList[index], "\n}\n");
14223   }
14224 }
14225
14226 void
14227 CrushCRs(text)
14228      char *text;
14229 {
14230   char *p = text;
14231   char *q = text;
14232   char ch;
14233
14234   do {
14235     ch = *p++;
14236     if (ch == '\r') continue;
14237     *q++ = ch;
14238   } while (ch != '\0');
14239 }
14240
14241 void
14242 AppendComment(index, text, addBraces)
14243      int index;
14244      char *text;
14245      Boolean addBraces; // [HGM] braces: tells if we should add {}
14246 {
14247     int oldlen, len;
14248     char *old;
14249
14250 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14251     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14252
14253     CrushCRs(text);
14254     while (*text == '\n') text++;
14255     len = strlen(text);
14256     while (len > 0 && text[len - 1] == '\n') len--;
14257
14258     if (len == 0) return;
14259
14260     if (commentList[index] != NULL) {
14261         old = commentList[index];
14262         oldlen = strlen(old);
14263         while(commentList[index][oldlen-1] ==  '\n')
14264           commentList[index][--oldlen] = NULLCHAR;
14265         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14266         safeStrCpy(commentList[index], old, oldlen + len + 6);
14267         free(old);
14268         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14269         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14270           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14271           while (*text == '\n') { text++; len--; }
14272           commentList[index][--oldlen] = NULLCHAR;
14273       }
14274         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14275         else          strcat(commentList[index], "\n");
14276         strcat(commentList[index], text);
14277         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14278         else          strcat(commentList[index], "\n");
14279     } else {
14280         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14281         if(addBraces)
14282           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14283         else commentList[index][0] = NULLCHAR;
14284         strcat(commentList[index], text);
14285         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14286         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14287     }
14288 }
14289
14290 static char * FindStr( char * text, char * sub_text )
14291 {
14292     char * result = strstr( text, sub_text );
14293
14294     if( result != NULL ) {
14295         result += strlen( sub_text );
14296     }
14297
14298     return result;
14299 }
14300
14301 /* [AS] Try to extract PV info from PGN comment */
14302 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14303 char *GetInfoFromComment( int index, char * text )
14304 {
14305     char * sep = text, *p;
14306
14307     if( text != NULL && index > 0 ) {
14308         int score = 0;
14309         int depth = 0;
14310         int time = -1, sec = 0, deci;
14311         char * s_eval = FindStr( text, "[%eval " );
14312         char * s_emt = FindStr( text, "[%emt " );
14313
14314         if( s_eval != NULL || s_emt != NULL ) {
14315             /* New style */
14316             char delim;
14317
14318             if( s_eval != NULL ) {
14319                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14320                     return text;
14321                 }
14322
14323                 if( delim != ']' ) {
14324                     return text;
14325                 }
14326             }
14327
14328             if( s_emt != NULL ) {
14329             }
14330                 return text;
14331         }
14332         else {
14333             /* We expect something like: [+|-]nnn.nn/dd */
14334             int score_lo = 0;
14335
14336             if(*text != '{') return text; // [HGM] braces: must be normal comment
14337
14338             sep = strchr( text, '/' );
14339             if( sep == NULL || sep < (text+4) ) {
14340                 return text;
14341             }
14342
14343             p = text;
14344             if(p[1] == '(') { // comment starts with PV
14345                p = strchr(p, ')'); // locate end of PV
14346                if(p == NULL || sep < p+5) return text;
14347                // at this point we have something like "{(.*) +0.23/6 ..."
14348                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14349                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14350                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14351             }
14352             time = -1; sec = -1; deci = -1;
14353             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14354                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14355                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14356                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14357                 return text;
14358             }
14359
14360             if( score_lo < 0 || score_lo >= 100 ) {
14361                 return text;
14362             }
14363
14364             if(sec >= 0) time = 600*time + 10*sec; else
14365             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14366
14367             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14368
14369             /* [HGM] PV time: now locate end of PV info */
14370             while( *++sep >= '0' && *sep <= '9'); // strip depth
14371             if(time >= 0)
14372             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14373             if(sec >= 0)
14374             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14375             if(deci >= 0)
14376             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14377             while(*sep == ' ') sep++;
14378         }
14379
14380         if( depth <= 0 ) {
14381             return text;
14382         }
14383
14384         if( time < 0 ) {
14385             time = -1;
14386         }
14387
14388         pvInfoList[index-1].depth = depth;
14389         pvInfoList[index-1].score = score;
14390         pvInfoList[index-1].time  = 10*time; // centi-sec
14391         if(*sep == '}') *sep = 0; else *--sep = '{';
14392         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14393     }
14394     return sep;
14395 }
14396
14397 void
14398 SendToProgram(message, cps)
14399      char *message;
14400      ChessProgramState *cps;
14401 {
14402     int count, outCount, error;
14403     char buf[MSG_SIZ];
14404
14405     if (cps->pr == NULL) return;
14406     Attention(cps);
14407
14408     if (appData.debugMode) {
14409         TimeMark now;
14410         GetTimeMark(&now);
14411         fprintf(debugFP, "%ld >%-6s: %s",
14412                 SubtractTimeMarks(&now, &programStartTime),
14413                 cps->which, message);
14414     }
14415
14416     count = strlen(message);
14417     outCount = OutputToProcess(cps->pr, message, count, &error);
14418     if (outCount < count && !exiting
14419                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14420       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14421       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14422         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14423             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14424                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14425                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14426                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14427             } else {
14428                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14429                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14430                 gameInfo.result = res;
14431             }
14432             gameInfo.resultDetails = StrSave(buf);
14433         }
14434         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14435         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14436     }
14437 }
14438
14439 void
14440 ReceiveFromProgram(isr, closure, message, count, error)
14441      InputSourceRef isr;
14442      VOIDSTAR closure;
14443      char *message;
14444      int count;
14445      int error;
14446 {
14447     char *end_str;
14448     char buf[MSG_SIZ];
14449     ChessProgramState *cps = (ChessProgramState *)closure;
14450
14451     if (isr != cps->isr) return; /* Killed intentionally */
14452     if (count <= 0) {
14453         if (count == 0) {
14454             RemoveInputSource(cps->isr);
14455             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14456             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14457                     _(cps->which), cps->program);
14458         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14459                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14460                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14461                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14462                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14463                 } else {
14464                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14465                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14466                     gameInfo.result = res;
14467                 }
14468                 gameInfo.resultDetails = StrSave(buf);
14469             }
14470             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14471             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14472         } else {
14473             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14474                     _(cps->which), cps->program);
14475             RemoveInputSource(cps->isr);
14476
14477             /* [AS] Program is misbehaving badly... kill it */
14478             if( count == -2 ) {
14479                 DestroyChildProcess( cps->pr, 9 );
14480                 cps->pr = NoProc;
14481             }
14482
14483             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14484         }
14485         return;
14486     }
14487
14488     if ((end_str = strchr(message, '\r')) != NULL)
14489       *end_str = NULLCHAR;
14490     if ((end_str = strchr(message, '\n')) != NULL)
14491       *end_str = NULLCHAR;
14492
14493     if (appData.debugMode) {
14494         TimeMark now; int print = 1;
14495         char *quote = ""; char c; int i;
14496
14497         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14498                 char start = message[0];
14499                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14500                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14501                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14502                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14503                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14504                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14505                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14506                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14507                    sscanf(message, "hint: %c", &c)!=1 && 
14508                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14509                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14510                     print = (appData.engineComments >= 2);
14511                 }
14512                 message[0] = start; // restore original message
14513         }
14514         if(print) {
14515                 GetTimeMark(&now);
14516                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14517                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14518                         quote,
14519                         message);
14520         }
14521     }
14522
14523     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14524     if (appData.icsEngineAnalyze) {
14525         if (strstr(message, "whisper") != NULL ||
14526              strstr(message, "kibitz") != NULL ||
14527             strstr(message, "tellics") != NULL) return;
14528     }
14529
14530     HandleMachineMove(message, cps);
14531 }
14532
14533
14534 void
14535 SendTimeControl(cps, mps, tc, inc, sd, st)
14536      ChessProgramState *cps;
14537      int mps, inc, sd, st;
14538      long tc;
14539 {
14540     char buf[MSG_SIZ];
14541     int seconds;
14542
14543     if( timeControl_2 > 0 ) {
14544         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14545             tc = timeControl_2;
14546         }
14547     }
14548     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14549     inc /= cps->timeOdds;
14550     st  /= cps->timeOdds;
14551
14552     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14553
14554     if (st > 0) {
14555       /* Set exact time per move, normally using st command */
14556       if (cps->stKludge) {
14557         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14558         seconds = st % 60;
14559         if (seconds == 0) {
14560           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14561         } else {
14562           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14563         }
14564       } else {
14565         snprintf(buf, MSG_SIZ, "st %d\n", st);
14566       }
14567     } else {
14568       /* Set conventional or incremental time control, using level command */
14569       if (seconds == 0) {
14570         /* Note old gnuchess bug -- minutes:seconds used to not work.
14571            Fixed in later versions, but still avoid :seconds
14572            when seconds is 0. */
14573         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14574       } else {
14575         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14576                  seconds, inc/1000.);
14577       }
14578     }
14579     SendToProgram(buf, cps);
14580
14581     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14582     /* Orthogonally, limit search to given depth */
14583     if (sd > 0) {
14584       if (cps->sdKludge) {
14585         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14586       } else {
14587         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14588       }
14589       SendToProgram(buf, cps);
14590     }
14591
14592     if(cps->nps >= 0) { /* [HGM] nps */
14593         if(cps->supportsNPS == FALSE)
14594           cps->nps = -1; // don't use if engine explicitly says not supported!
14595         else {
14596           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14597           SendToProgram(buf, cps);
14598         }
14599     }
14600 }
14601
14602 ChessProgramState *WhitePlayer()
14603 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14604 {
14605     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14606        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14607         return &second;
14608     return &first;
14609 }
14610
14611 void
14612 SendTimeRemaining(cps, machineWhite)
14613      ChessProgramState *cps;
14614      int /*boolean*/ machineWhite;
14615 {
14616     char message[MSG_SIZ];
14617     long time, otime;
14618
14619     /* Note: this routine must be called when the clocks are stopped
14620        or when they have *just* been set or switched; otherwise
14621        it will be off by the time since the current tick started.
14622     */
14623     if (machineWhite) {
14624         time = whiteTimeRemaining / 10;
14625         otime = blackTimeRemaining / 10;
14626     } else {
14627         time = blackTimeRemaining / 10;
14628         otime = whiteTimeRemaining / 10;
14629     }
14630     /* [HGM] translate opponent's time by time-odds factor */
14631     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14632     if (appData.debugMode) {
14633         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14634     }
14635
14636     if (time <= 0) time = 1;
14637     if (otime <= 0) otime = 1;
14638
14639     snprintf(message, MSG_SIZ, "time %ld\n", time);
14640     SendToProgram(message, cps);
14641
14642     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14643     SendToProgram(message, cps);
14644 }
14645
14646 int
14647 BoolFeature(p, name, loc, cps)
14648      char **p;
14649      char *name;
14650      int *loc;
14651      ChessProgramState *cps;
14652 {
14653   char buf[MSG_SIZ];
14654   int len = strlen(name);
14655   int val;
14656
14657   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14658     (*p) += len + 1;
14659     sscanf(*p, "%d", &val);
14660     *loc = (val != 0);
14661     while (**p && **p != ' ')
14662       (*p)++;
14663     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14664     SendToProgram(buf, cps);
14665     return TRUE;
14666   }
14667   return FALSE;
14668 }
14669
14670 int
14671 IntFeature(p, name, loc, cps)
14672      char **p;
14673      char *name;
14674      int *loc;
14675      ChessProgramState *cps;
14676 {
14677   char buf[MSG_SIZ];
14678   int len = strlen(name);
14679   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14680     (*p) += len + 1;
14681     sscanf(*p, "%d", loc);
14682     while (**p && **p != ' ') (*p)++;
14683     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14684     SendToProgram(buf, cps);
14685     return TRUE;
14686   }
14687   return FALSE;
14688 }
14689
14690 int
14691 StringFeature(p, name, loc, cps)
14692      char **p;
14693      char *name;
14694      char loc[];
14695      ChessProgramState *cps;
14696 {
14697   char buf[MSG_SIZ];
14698   int len = strlen(name);
14699   if (strncmp((*p), name, len) == 0
14700       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14701     (*p) += len + 2;
14702     sscanf(*p, "%[^\"]", loc);
14703     while (**p && **p != '\"') (*p)++;
14704     if (**p == '\"') (*p)++;
14705     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14706     SendToProgram(buf, cps);
14707     return TRUE;
14708   }
14709   return FALSE;
14710 }
14711
14712 int
14713 ParseOption(Option *opt, ChessProgramState *cps)
14714 // [HGM] options: process the string that defines an engine option, and determine
14715 // name, type, default value, and allowed value range
14716 {
14717         char *p, *q, buf[MSG_SIZ];
14718         int n, min = (-1)<<31, max = 1<<31, def;
14719
14720         if(p = strstr(opt->name, " -spin ")) {
14721             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14722             if(max < min) max = min; // enforce consistency
14723             if(def < min) def = min;
14724             if(def > max) def = max;
14725             opt->value = def;
14726             opt->min = min;
14727             opt->max = max;
14728             opt->type = Spin;
14729         } else if((p = strstr(opt->name, " -slider "))) {
14730             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14731             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14732             if(max < min) max = min; // enforce consistency
14733             if(def < min) def = min;
14734             if(def > max) def = max;
14735             opt->value = def;
14736             opt->min = min;
14737             opt->max = max;
14738             opt->type = Spin; // Slider;
14739         } else if((p = strstr(opt->name, " -string "))) {
14740             opt->textValue = p+9;
14741             opt->type = TextBox;
14742         } else if((p = strstr(opt->name, " -file "))) {
14743             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14744             opt->textValue = p+7;
14745             opt->type = FileName; // FileName;
14746         } else if((p = strstr(opt->name, " -path "))) {
14747             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14748             opt->textValue = p+7;
14749             opt->type = PathName; // PathName;
14750         } else if(p = strstr(opt->name, " -check ")) {
14751             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14752             opt->value = (def != 0);
14753             opt->type = CheckBox;
14754         } else if(p = strstr(opt->name, " -combo ")) {
14755             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14756             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14757             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14758             opt->value = n = 0;
14759             while(q = StrStr(q, " /// ")) {
14760                 n++; *q = 0;    // count choices, and null-terminate each of them
14761                 q += 5;
14762                 if(*q == '*') { // remember default, which is marked with * prefix
14763                     q++;
14764                     opt->value = n;
14765                 }
14766                 cps->comboList[cps->comboCnt++] = q;
14767             }
14768             cps->comboList[cps->comboCnt++] = NULL;
14769             opt->max = n + 1;
14770             opt->type = ComboBox;
14771         } else if(p = strstr(opt->name, " -button")) {
14772             opt->type = Button;
14773         } else if(p = strstr(opt->name, " -save")) {
14774             opt->type = SaveButton;
14775         } else return FALSE;
14776         *p = 0; // terminate option name
14777         // now look if the command-line options define a setting for this engine option.
14778         if(cps->optionSettings && cps->optionSettings[0])
14779             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14780         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14781           snprintf(buf, MSG_SIZ, "option %s", p);
14782                 if(p = strstr(buf, ",")) *p = 0;
14783                 if(q = strchr(buf, '=')) switch(opt->type) {
14784                     case ComboBox:
14785                         for(n=0; n<opt->max; n++)
14786                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14787                         break;
14788                     case TextBox:
14789                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14790                         break;
14791                     case Spin:
14792                     case CheckBox:
14793                         opt->value = atoi(q+1);
14794                     default:
14795                         break;
14796                 }
14797                 strcat(buf, "\n");
14798                 SendToProgram(buf, cps);
14799         }
14800         return TRUE;
14801 }
14802
14803 void
14804 FeatureDone(cps, val)
14805      ChessProgramState* cps;
14806      int val;
14807 {
14808   DelayedEventCallback cb = GetDelayedEvent();
14809   if ((cb == InitBackEnd3 && cps == &first) ||
14810       (cb == SettingsMenuIfReady && cps == &second) ||
14811       (cb == LoadEngine) ||
14812       (cb == TwoMachinesEventIfReady)) {
14813     CancelDelayedEvent();
14814     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14815   }
14816   cps->initDone = val;
14817 }
14818
14819 /* Parse feature command from engine */
14820 void
14821 ParseFeatures(args, cps)
14822      char* args;
14823      ChessProgramState *cps;
14824 {
14825   char *p = args;
14826   char *q;
14827   int val;
14828   char buf[MSG_SIZ];
14829
14830   for (;;) {
14831     while (*p == ' ') p++;
14832     if (*p == NULLCHAR) return;
14833
14834     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14835     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14836     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14837     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14838     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14839     if (BoolFeature(&p, "reuse", &val, cps)) {
14840       /* Engine can disable reuse, but can't enable it if user said no */
14841       if (!val) cps->reuse = FALSE;
14842       continue;
14843     }
14844     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14845     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14846       if (gameMode == TwoMachinesPlay) {
14847         DisplayTwoMachinesTitle();
14848       } else {
14849         DisplayTitle("");
14850       }
14851       continue;
14852     }
14853     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14854     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14855     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14856     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14857     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14858     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14859     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14860     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14861     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14862     if (IntFeature(&p, "done", &val, cps)) {
14863       FeatureDone(cps, val);
14864       continue;
14865     }
14866     /* Added by Tord: */
14867     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14868     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14869     /* End of additions by Tord */
14870
14871     /* [HGM] added features: */
14872     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14873     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14874     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14875     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14876     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14877     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14878     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14879         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14880           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14881             SendToProgram(buf, cps);
14882             continue;
14883         }
14884         if(cps->nrOptions >= MAX_OPTIONS) {
14885             cps->nrOptions--;
14886             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14887             DisplayError(buf, 0);
14888         }
14889         continue;
14890     }
14891     /* End of additions by HGM */
14892
14893     /* unknown feature: complain and skip */
14894     q = p;
14895     while (*q && *q != '=') q++;
14896     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14897     SendToProgram(buf, cps);
14898     p = q;
14899     if (*p == '=') {
14900       p++;
14901       if (*p == '\"') {
14902         p++;
14903         while (*p && *p != '\"') p++;
14904         if (*p == '\"') p++;
14905       } else {
14906         while (*p && *p != ' ') p++;
14907       }
14908     }
14909   }
14910
14911 }
14912
14913 void
14914 PeriodicUpdatesEvent(newState)
14915      int newState;
14916 {
14917     if (newState == appData.periodicUpdates)
14918       return;
14919
14920     appData.periodicUpdates=newState;
14921
14922     /* Display type changes, so update it now */
14923 //    DisplayAnalysis();
14924
14925     /* Get the ball rolling again... */
14926     if (newState) {
14927         AnalysisPeriodicEvent(1);
14928         StartAnalysisClock();
14929     }
14930 }
14931
14932 void
14933 PonderNextMoveEvent(newState)
14934      int newState;
14935 {
14936     if (newState == appData.ponderNextMove) return;
14937     if (gameMode == EditPosition) EditPositionDone(TRUE);
14938     if (newState) {
14939         SendToProgram("hard\n", &first);
14940         if (gameMode == TwoMachinesPlay) {
14941             SendToProgram("hard\n", &second);
14942         }
14943     } else {
14944         SendToProgram("easy\n", &first);
14945         thinkOutput[0] = NULLCHAR;
14946         if (gameMode == TwoMachinesPlay) {
14947             SendToProgram("easy\n", &second);
14948         }
14949     }
14950     appData.ponderNextMove = newState;
14951 }
14952
14953 void
14954 NewSettingEvent(option, feature, command, value)
14955      char *command;
14956      int option, value, *feature;
14957 {
14958     char buf[MSG_SIZ];
14959
14960     if (gameMode == EditPosition) EditPositionDone(TRUE);
14961     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14962     if(feature == NULL || *feature) SendToProgram(buf, &first);
14963     if (gameMode == TwoMachinesPlay) {
14964         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14965     }
14966 }
14967
14968 void
14969 ShowThinkingEvent()
14970 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14971 {
14972     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14973     int newState = appData.showThinking
14974         // [HGM] thinking: other features now need thinking output as well
14975         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14976
14977     if (oldState == newState) return;
14978     oldState = newState;
14979     if (gameMode == EditPosition) EditPositionDone(TRUE);
14980     if (oldState) {
14981         SendToProgram("post\n", &first);
14982         if (gameMode == TwoMachinesPlay) {
14983             SendToProgram("post\n", &second);
14984         }
14985     } else {
14986         SendToProgram("nopost\n", &first);
14987         thinkOutput[0] = NULLCHAR;
14988         if (gameMode == TwoMachinesPlay) {
14989             SendToProgram("nopost\n", &second);
14990         }
14991     }
14992 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14993 }
14994
14995 void
14996 AskQuestionEvent(title, question, replyPrefix, which)
14997      char *title; char *question; char *replyPrefix; char *which;
14998 {
14999   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15000   if (pr == NoProc) return;
15001   AskQuestion(title, question, replyPrefix, pr);
15002 }
15003
15004 void
15005 TypeInEvent(char firstChar)
15006 {
15007     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15008         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15009         gameMode == AnalyzeMode || gameMode == EditGame || \r
15010         gameMode == EditPosition || gameMode == IcsExamining ||\r
15011         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15012         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15013                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15014                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15015         gameMode == Training) PopUpMoveDialog(firstChar);
15016 }
15017
15018 void
15019 TypeInDoneEvent(char *move)
15020 {
15021         Board board;
15022         int n, fromX, fromY, toX, toY;
15023         char promoChar;
15024         ChessMove moveType;\r
15025
15026         // [HGM] FENedit\r
15027         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15028                 EditPositionPasteFEN(move);\r
15029                 return;\r
15030         }\r
15031         // [HGM] movenum: allow move number to be typed in any mode\r
15032         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15033           ToNrEvent(2*n-1);\r
15034           return;\r
15035         }\r
15036
15037       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15038         gameMode != Training) {\r
15039         DisplayMoveError(_("Displayed move is not current"));\r
15040       } else {\r
15041         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15042           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15043         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15044         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15045           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15046           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15047         } else {\r
15048           DisplayMoveError(_("Could not parse move"));\r
15049         }
15050       }\r
15051 }\r
15052
15053 void
15054 DisplayMove(moveNumber)
15055      int moveNumber;
15056 {
15057     char message[MSG_SIZ];
15058     char res[MSG_SIZ];
15059     char cpThinkOutput[MSG_SIZ];
15060
15061     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15062
15063     if (moveNumber == forwardMostMove - 1 ||
15064         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15065
15066         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15067
15068         if (strchr(cpThinkOutput, '\n')) {
15069             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15070         }
15071     } else {
15072         *cpThinkOutput = NULLCHAR;
15073     }
15074
15075     /* [AS] Hide thinking from human user */
15076     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15077         *cpThinkOutput = NULLCHAR;
15078         if( thinkOutput[0] != NULLCHAR ) {
15079             int i;
15080
15081             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15082                 cpThinkOutput[i] = '.';
15083             }
15084             cpThinkOutput[i] = NULLCHAR;
15085             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15086         }
15087     }
15088
15089     if (moveNumber == forwardMostMove - 1 &&
15090         gameInfo.resultDetails != NULL) {
15091         if (gameInfo.resultDetails[0] == NULLCHAR) {
15092           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15093         } else {
15094           snprintf(res, MSG_SIZ, " {%s} %s",
15095                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15096         }
15097     } else {
15098         res[0] = NULLCHAR;
15099     }
15100
15101     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15102         DisplayMessage(res, cpThinkOutput);
15103     } else {
15104       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15105                 WhiteOnMove(moveNumber) ? " " : ".. ",
15106                 parseList[moveNumber], res);
15107         DisplayMessage(message, cpThinkOutput);
15108     }
15109 }
15110
15111 void
15112 DisplayComment(moveNumber, text)
15113      int moveNumber;
15114      char *text;
15115 {
15116     char title[MSG_SIZ];
15117     char buf[8000]; // comment can be long!
15118     int score, depth;
15119
15120     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15121       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15122     } else {
15123       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15124               WhiteOnMove(moveNumber) ? " " : ".. ",
15125               parseList[moveNumber]);
15126     }
15127     // [HGM] PV info: display PV info together with (or as) comment
15128     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15129       if(text == NULL) text = "";
15130       score = pvInfoList[moveNumber].score;
15131       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15132               depth, (pvInfoList[moveNumber].time+50)/100, text);
15133       text = buf;
15134     }
15135     if (text != NULL && (appData.autoDisplayComment || commentUp))
15136         CommentPopUp(title, text);
15137 }
15138
15139 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15140  * might be busy thinking or pondering.  It can be omitted if your
15141  * gnuchess is configured to stop thinking immediately on any user
15142  * input.  However, that gnuchess feature depends on the FIONREAD
15143  * ioctl, which does not work properly on some flavors of Unix.
15144  */
15145 void
15146 Attention(cps)
15147      ChessProgramState *cps;
15148 {
15149 #if ATTENTION
15150     if (!cps->useSigint) return;
15151     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15152     switch (gameMode) {
15153       case MachinePlaysWhite:
15154       case MachinePlaysBlack:
15155       case TwoMachinesPlay:
15156       case IcsPlayingWhite:
15157       case IcsPlayingBlack:
15158       case AnalyzeMode:
15159       case AnalyzeFile:
15160         /* Skip if we know it isn't thinking */
15161         if (!cps->maybeThinking) return;
15162         if (appData.debugMode)
15163           fprintf(debugFP, "Interrupting %s\n", cps->which);
15164         InterruptChildProcess(cps->pr);
15165         cps->maybeThinking = FALSE;
15166         break;
15167       default:
15168         break;
15169     }
15170 #endif /*ATTENTION*/
15171 }
15172
15173 int
15174 CheckFlags()
15175 {
15176     if (whiteTimeRemaining <= 0) {
15177         if (!whiteFlag) {
15178             whiteFlag = TRUE;
15179             if (appData.icsActive) {
15180                 if (appData.autoCallFlag &&
15181                     gameMode == IcsPlayingBlack && !blackFlag) {
15182                   SendToICS(ics_prefix);
15183                   SendToICS("flag\n");
15184                 }
15185             } else {
15186                 if (blackFlag) {
15187                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15188                 } else {
15189                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15190                     if (appData.autoCallFlag) {
15191                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15192                         return TRUE;
15193                     }
15194                 }
15195             }
15196         }
15197     }
15198     if (blackTimeRemaining <= 0) {
15199         if (!blackFlag) {
15200             blackFlag = TRUE;
15201             if (appData.icsActive) {
15202                 if (appData.autoCallFlag &&
15203                     gameMode == IcsPlayingWhite && !whiteFlag) {
15204                   SendToICS(ics_prefix);
15205                   SendToICS("flag\n");
15206                 }
15207             } else {
15208                 if (whiteFlag) {
15209                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15210                 } else {
15211                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15212                     if (appData.autoCallFlag) {
15213                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15214                         return TRUE;
15215                     }
15216                 }
15217             }
15218         }
15219     }
15220     return FALSE;
15221 }
15222
15223 void
15224 CheckTimeControl()
15225 {
15226     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15227         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15228
15229     /*
15230      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15231      */
15232     if ( !WhiteOnMove(forwardMostMove) ) {
15233         /* White made time control */
15234         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15235         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15236         /* [HGM] time odds: correct new time quota for time odds! */
15237                                             / WhitePlayer()->timeOdds;
15238         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15239     } else {
15240         lastBlack -= blackTimeRemaining;
15241         /* Black made time control */
15242         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15243                                             / WhitePlayer()->other->timeOdds;
15244         lastWhite = whiteTimeRemaining;
15245     }
15246 }
15247
15248 void
15249 DisplayBothClocks()
15250 {
15251     int wom = gameMode == EditPosition ?
15252       !blackPlaysFirst : WhiteOnMove(currentMove);
15253     DisplayWhiteClock(whiteTimeRemaining, wom);
15254     DisplayBlackClock(blackTimeRemaining, !wom);
15255 }
15256
15257
15258 /* Timekeeping seems to be a portability nightmare.  I think everyone
15259    has ftime(), but I'm really not sure, so I'm including some ifdefs
15260    to use other calls if you don't.  Clocks will be less accurate if
15261    you have neither ftime nor gettimeofday.
15262 */
15263
15264 /* VS 2008 requires the #include outside of the function */
15265 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15266 #include <sys/timeb.h>
15267 #endif
15268
15269 /* Get the current time as a TimeMark */
15270 void
15271 GetTimeMark(tm)
15272      TimeMark *tm;
15273 {
15274 #if HAVE_GETTIMEOFDAY
15275
15276     struct timeval timeVal;
15277     struct timezone timeZone;
15278
15279     gettimeofday(&timeVal, &timeZone);
15280     tm->sec = (long) timeVal.tv_sec;
15281     tm->ms = (int) (timeVal.tv_usec / 1000L);
15282
15283 #else /*!HAVE_GETTIMEOFDAY*/
15284 #if HAVE_FTIME
15285
15286 // include <sys/timeb.h> / moved to just above start of function
15287     struct timeb timeB;
15288
15289     ftime(&timeB);
15290     tm->sec = (long) timeB.time;
15291     tm->ms = (int) timeB.millitm;
15292
15293 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15294     tm->sec = (long) time(NULL);
15295     tm->ms = 0;
15296 #endif
15297 #endif
15298 }
15299
15300 /* Return the difference in milliseconds between two
15301    time marks.  We assume the difference will fit in a long!
15302 */
15303 long
15304 SubtractTimeMarks(tm2, tm1)
15305      TimeMark *tm2, *tm1;
15306 {
15307     return 1000L*(tm2->sec - tm1->sec) +
15308            (long) (tm2->ms - tm1->ms);
15309 }
15310
15311
15312 /*
15313  * Code to manage the game clocks.
15314  *
15315  * In tournament play, black starts the clock and then white makes a move.
15316  * We give the human user a slight advantage if he is playing white---the
15317  * clocks don't run until he makes his first move, so it takes zero time.
15318  * Also, we don't account for network lag, so we could get out of sync
15319  * with GNU Chess's clock -- but then, referees are always right.
15320  */
15321
15322 static TimeMark tickStartTM;
15323 static long intendedTickLength;
15324
15325 long
15326 NextTickLength(timeRemaining)
15327      long timeRemaining;
15328 {
15329     long nominalTickLength, nextTickLength;
15330
15331     if (timeRemaining > 0L && timeRemaining <= 10000L)
15332       nominalTickLength = 100L;
15333     else
15334       nominalTickLength = 1000L;
15335     nextTickLength = timeRemaining % nominalTickLength;
15336     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15337
15338     return nextTickLength;
15339 }
15340
15341 /* Adjust clock one minute up or down */
15342 void
15343 AdjustClock(Boolean which, int dir)
15344 {
15345     if(which) blackTimeRemaining += 60000*dir;
15346     else      whiteTimeRemaining += 60000*dir;
15347     DisplayBothClocks();
15348 }
15349
15350 /* Stop clocks and reset to a fresh time control */
15351 void
15352 ResetClocks()
15353 {
15354     (void) StopClockTimer();
15355     if (appData.icsActive) {
15356         whiteTimeRemaining = blackTimeRemaining = 0;
15357     } else if (searchTime) {
15358         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15359         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15360     } else { /* [HGM] correct new time quote for time odds */
15361         whiteTC = blackTC = fullTimeControlString;
15362         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15363         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15364     }
15365     if (whiteFlag || blackFlag) {
15366         DisplayTitle("");
15367         whiteFlag = blackFlag = FALSE;
15368     }
15369     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15370     DisplayBothClocks();
15371 }
15372
15373 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15374
15375 /* Decrement running clock by amount of time that has passed */
15376 void
15377 DecrementClocks()
15378 {
15379     long timeRemaining;
15380     long lastTickLength, fudge;
15381     TimeMark now;
15382
15383     if (!appData.clockMode) return;
15384     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15385
15386     GetTimeMark(&now);
15387
15388     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15389
15390     /* Fudge if we woke up a little too soon */
15391     fudge = intendedTickLength - lastTickLength;
15392     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15393
15394     if (WhiteOnMove(forwardMostMove)) {
15395         if(whiteNPS >= 0) lastTickLength = 0;
15396         timeRemaining = whiteTimeRemaining -= lastTickLength;
15397         if(timeRemaining < 0 && !appData.icsActive) {
15398             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15399             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15400                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15401                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15402             }
15403         }
15404         DisplayWhiteClock(whiteTimeRemaining - fudge,
15405                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15406     } else {
15407         if(blackNPS >= 0) lastTickLength = 0;
15408         timeRemaining = blackTimeRemaining -= lastTickLength;
15409         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15410             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15411             if(suddenDeath) {
15412                 blackStartMove = forwardMostMove;
15413                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15414             }
15415         }
15416         DisplayBlackClock(blackTimeRemaining - fudge,
15417                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15418     }
15419     if (CheckFlags()) return;
15420
15421     tickStartTM = now;
15422     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15423     StartClockTimer(intendedTickLength);
15424
15425     /* if the time remaining has fallen below the alarm threshold, sound the
15426      * alarm. if the alarm has sounded and (due to a takeback or time control
15427      * with increment) the time remaining has increased to a level above the
15428      * threshold, reset the alarm so it can sound again.
15429      */
15430
15431     if (appData.icsActive && appData.icsAlarm) {
15432
15433         /* make sure we are dealing with the user's clock */
15434         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15435                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15436            )) return;
15437
15438         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15439             alarmSounded = FALSE;
15440         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15441             PlayAlarmSound();
15442             alarmSounded = TRUE;
15443         }
15444     }
15445 }
15446
15447
15448 /* A player has just moved, so stop the previously running
15449    clock and (if in clock mode) start the other one.
15450    We redisplay both clocks in case we're in ICS mode, because
15451    ICS gives us an update to both clocks after every move.
15452    Note that this routine is called *after* forwardMostMove
15453    is updated, so the last fractional tick must be subtracted
15454    from the color that is *not* on move now.
15455 */
15456 void
15457 SwitchClocks(int newMoveNr)
15458 {
15459     long lastTickLength;
15460     TimeMark now;
15461     int flagged = FALSE;
15462
15463     GetTimeMark(&now);
15464
15465     if (StopClockTimer() && appData.clockMode) {
15466         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15467         if (!WhiteOnMove(forwardMostMove)) {
15468             if(blackNPS >= 0) lastTickLength = 0;
15469             blackTimeRemaining -= lastTickLength;
15470            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15471 //         if(pvInfoList[forwardMostMove].time == -1)
15472                  pvInfoList[forwardMostMove].time =               // use GUI time
15473                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15474         } else {
15475            if(whiteNPS >= 0) lastTickLength = 0;
15476            whiteTimeRemaining -= lastTickLength;
15477            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15478 //         if(pvInfoList[forwardMostMove].time == -1)
15479                  pvInfoList[forwardMostMove].time =
15480                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15481         }
15482         flagged = CheckFlags();
15483     }
15484     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15485     CheckTimeControl();
15486
15487     if (flagged || !appData.clockMode) return;
15488
15489     switch (gameMode) {
15490       case MachinePlaysBlack:
15491       case MachinePlaysWhite:
15492       case BeginningOfGame:
15493         if (pausing) return;
15494         break;
15495
15496       case EditGame:
15497       case PlayFromGameFile:
15498       case IcsExamining:
15499         return;
15500
15501       default:
15502         break;
15503     }
15504
15505     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15506         if(WhiteOnMove(forwardMostMove))
15507              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15508         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15509     }
15510
15511     tickStartTM = now;
15512     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15513       whiteTimeRemaining : blackTimeRemaining);
15514     StartClockTimer(intendedTickLength);
15515 }
15516
15517
15518 /* Stop both clocks */
15519 void
15520 StopClocks()
15521 {
15522     long lastTickLength;
15523     TimeMark now;
15524
15525     if (!StopClockTimer()) return;
15526     if (!appData.clockMode) return;
15527
15528     GetTimeMark(&now);
15529
15530     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15531     if (WhiteOnMove(forwardMostMove)) {
15532         if(whiteNPS >= 0) lastTickLength = 0;
15533         whiteTimeRemaining -= lastTickLength;
15534         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15535     } else {
15536         if(blackNPS >= 0) lastTickLength = 0;
15537         blackTimeRemaining -= lastTickLength;
15538         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15539     }
15540     CheckFlags();
15541 }
15542
15543 /* Start clock of player on move.  Time may have been reset, so
15544    if clock is already running, stop and restart it. */
15545 void
15546 StartClocks()
15547 {
15548     (void) StopClockTimer(); /* in case it was running already */
15549     DisplayBothClocks();
15550     if (CheckFlags()) return;
15551
15552     if (!appData.clockMode) return;
15553     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15554
15555     GetTimeMark(&tickStartTM);
15556     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15557       whiteTimeRemaining : blackTimeRemaining);
15558
15559    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15560     whiteNPS = blackNPS = -1;
15561     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15562        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15563         whiteNPS = first.nps;
15564     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15565        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15566         blackNPS = first.nps;
15567     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15568         whiteNPS = second.nps;
15569     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15570         blackNPS = second.nps;
15571     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15572
15573     StartClockTimer(intendedTickLength);
15574 }
15575
15576 char *
15577 TimeString(ms)
15578      long ms;
15579 {
15580     long second, minute, hour, day;
15581     char *sign = "";
15582     static char buf[32];
15583
15584     if (ms > 0 && ms <= 9900) {
15585       /* convert milliseconds to tenths, rounding up */
15586       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15587
15588       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15589       return buf;
15590     }
15591
15592     /* convert milliseconds to seconds, rounding up */
15593     /* use floating point to avoid strangeness of integer division
15594        with negative dividends on many machines */
15595     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15596
15597     if (second < 0) {
15598         sign = "-";
15599         second = -second;
15600     }
15601
15602     day = second / (60 * 60 * 24);
15603     second = second % (60 * 60 * 24);
15604     hour = second / (60 * 60);
15605     second = second % (60 * 60);
15606     minute = second / 60;
15607     second = second % 60;
15608
15609     if (day > 0)
15610       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15611               sign, day, hour, minute, second);
15612     else if (hour > 0)
15613       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15614     else
15615       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15616
15617     return buf;
15618 }
15619
15620
15621 /*
15622  * This is necessary because some C libraries aren't ANSI C compliant yet.
15623  */
15624 char *
15625 StrStr(string, match)
15626      char *string, *match;
15627 {
15628     int i, length;
15629
15630     length = strlen(match);
15631
15632     for (i = strlen(string) - length; i >= 0; i--, string++)
15633       if (!strncmp(match, string, length))
15634         return string;
15635
15636     return NULL;
15637 }
15638
15639 char *
15640 StrCaseStr(string, match)
15641      char *string, *match;
15642 {
15643     int i, j, length;
15644
15645     length = strlen(match);
15646
15647     for (i = strlen(string) - length; i >= 0; i--, string++) {
15648         for (j = 0; j < length; j++) {
15649             if (ToLower(match[j]) != ToLower(string[j]))
15650               break;
15651         }
15652         if (j == length) return string;
15653     }
15654
15655     return NULL;
15656 }
15657
15658 #ifndef _amigados
15659 int
15660 StrCaseCmp(s1, s2)
15661      char *s1, *s2;
15662 {
15663     char c1, c2;
15664
15665     for (;;) {
15666         c1 = ToLower(*s1++);
15667         c2 = ToLower(*s2++);
15668         if (c1 > c2) return 1;
15669         if (c1 < c2) return -1;
15670         if (c1 == NULLCHAR) return 0;
15671     }
15672 }
15673
15674
15675 int
15676 ToLower(c)
15677      int c;
15678 {
15679     return isupper(c) ? tolower(c) : c;
15680 }
15681
15682
15683 int
15684 ToUpper(c)
15685      int c;
15686 {
15687     return islower(c) ? toupper(c) : c;
15688 }
15689 #endif /* !_amigados    */
15690
15691 char *
15692 StrSave(s)
15693      char *s;
15694 {
15695   char *ret;
15696
15697   if ((ret = (char *) malloc(strlen(s) + 1)))
15698     {
15699       safeStrCpy(ret, s, strlen(s)+1);
15700     }
15701   return ret;
15702 }
15703
15704 char *
15705 StrSavePtr(s, savePtr)
15706      char *s, **savePtr;
15707 {
15708     if (*savePtr) {
15709         free(*savePtr);
15710     }
15711     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15712       safeStrCpy(*savePtr, s, strlen(s)+1);
15713     }
15714     return(*savePtr);
15715 }
15716
15717 char *
15718 PGNDate()
15719 {
15720     time_t clock;
15721     struct tm *tm;
15722     char buf[MSG_SIZ];
15723
15724     clock = time((time_t *)NULL);
15725     tm = localtime(&clock);
15726     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15727             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15728     return StrSave(buf);
15729 }
15730
15731
15732 char *
15733 PositionToFEN(move, overrideCastling)
15734      int move;
15735      char *overrideCastling;
15736 {
15737     int i, j, fromX, fromY, toX, toY;
15738     int whiteToPlay;
15739     char buf[128];
15740     char *p, *q;
15741     int emptycount;
15742     ChessSquare piece;
15743
15744     whiteToPlay = (gameMode == EditPosition) ?
15745       !blackPlaysFirst : (move % 2 == 0);
15746     p = buf;
15747
15748     /* Piece placement data */
15749     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15750         emptycount = 0;
15751         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15752             if (boards[move][i][j] == EmptySquare) {
15753                 emptycount++;
15754             } else { ChessSquare piece = boards[move][i][j];
15755                 if (emptycount > 0) {
15756                     if(emptycount<10) /* [HGM] can be >= 10 */
15757                         *p++ = '0' + emptycount;
15758                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15759                     emptycount = 0;
15760                 }
15761                 if(PieceToChar(piece) == '+') {
15762                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15763                     *p++ = '+';
15764                     piece = (ChessSquare)(DEMOTED piece);
15765                 }
15766                 *p++ = PieceToChar(piece);
15767                 if(p[-1] == '~') {
15768                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15769                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15770                     *p++ = '~';
15771                 }
15772             }
15773         }
15774         if (emptycount > 0) {
15775             if(emptycount<10) /* [HGM] can be >= 10 */
15776                 *p++ = '0' + emptycount;
15777             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15778             emptycount = 0;
15779         }
15780         *p++ = '/';
15781     }
15782     *(p - 1) = ' ';
15783
15784     /* [HGM] print Crazyhouse or Shogi holdings */
15785     if( gameInfo.holdingsWidth ) {
15786         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15787         q = p;
15788         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15789             piece = boards[move][i][BOARD_WIDTH-1];
15790             if( piece != EmptySquare )
15791               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15792                   *p++ = PieceToChar(piece);
15793         }
15794         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15795             piece = boards[move][BOARD_HEIGHT-i-1][0];
15796             if( piece != EmptySquare )
15797               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15798                   *p++ = PieceToChar(piece);
15799         }
15800
15801         if( q == p ) *p++ = '-';
15802         *p++ = ']';
15803         *p++ = ' ';
15804     }
15805
15806     /* Active color */
15807     *p++ = whiteToPlay ? 'w' : 'b';
15808     *p++ = ' ';
15809
15810   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15811     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15812   } else {
15813   if(nrCastlingRights) {
15814      q = p;
15815      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15816        /* [HGM] write directly from rights */
15817            if(boards[move][CASTLING][2] != NoRights &&
15818               boards[move][CASTLING][0] != NoRights   )
15819                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15820            if(boards[move][CASTLING][2] != NoRights &&
15821               boards[move][CASTLING][1] != NoRights   )
15822                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15823            if(boards[move][CASTLING][5] != NoRights &&
15824               boards[move][CASTLING][3] != NoRights   )
15825                 *p++ = boards[move][CASTLING][3] + AAA;
15826            if(boards[move][CASTLING][5] != NoRights &&
15827               boards[move][CASTLING][4] != NoRights   )
15828                 *p++ = boards[move][CASTLING][4] + AAA;
15829      } else {
15830
15831         /* [HGM] write true castling rights */
15832         if( nrCastlingRights == 6 ) {
15833             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15834                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15835             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15836                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15837             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15838                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15839             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15840                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15841         }
15842      }
15843      if (q == p) *p++ = '-'; /* No castling rights */
15844      *p++ = ' ';
15845   }
15846
15847   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15848      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15849     /* En passant target square */
15850     if (move > backwardMostMove) {
15851         fromX = moveList[move - 1][0] - AAA;
15852         fromY = moveList[move - 1][1] - ONE;
15853         toX = moveList[move - 1][2] - AAA;
15854         toY = moveList[move - 1][3] - ONE;
15855         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15856             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15857             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15858             fromX == toX) {
15859             /* 2-square pawn move just happened */
15860             *p++ = toX + AAA;
15861             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15862         } else {
15863             *p++ = '-';
15864         }
15865     } else if(move == backwardMostMove) {
15866         // [HGM] perhaps we should always do it like this, and forget the above?
15867         if((signed char)boards[move][EP_STATUS] >= 0) {
15868             *p++ = boards[move][EP_STATUS] + AAA;
15869             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15870         } else {
15871             *p++ = '-';
15872         }
15873     } else {
15874         *p++ = '-';
15875     }
15876     *p++ = ' ';
15877   }
15878   }
15879
15880     /* [HGM] find reversible plies */
15881     {   int i = 0, j=move;
15882
15883         if (appData.debugMode) { int k;
15884             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15885             for(k=backwardMostMove; k<=forwardMostMove; k++)
15886                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15887
15888         }
15889
15890         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15891         if( j == backwardMostMove ) i += initialRulePlies;
15892         sprintf(p, "%d ", i);
15893         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15894     }
15895     /* Fullmove number */
15896     sprintf(p, "%d", (move / 2) + 1);
15897
15898     return StrSave(buf);
15899 }
15900
15901 Boolean
15902 ParseFEN(board, blackPlaysFirst, fen)
15903     Board board;
15904      int *blackPlaysFirst;
15905      char *fen;
15906 {
15907     int i, j;
15908     char *p, c;
15909     int emptycount;
15910     ChessSquare piece;
15911
15912     p = fen;
15913
15914     /* [HGM] by default clear Crazyhouse holdings, if present */
15915     if(gameInfo.holdingsWidth) {
15916        for(i=0; i<BOARD_HEIGHT; i++) {
15917            board[i][0]             = EmptySquare; /* black holdings */
15918            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15919            board[i][1]             = (ChessSquare) 0; /* black counts */
15920            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15921        }
15922     }
15923
15924     /* Piece placement data */
15925     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15926         j = 0;
15927         for (;;) {
15928             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15929                 if (*p == '/') p++;
15930                 emptycount = gameInfo.boardWidth - j;
15931                 while (emptycount--)
15932                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15933                 break;
15934 #if(BOARD_FILES >= 10)
15935             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15936                 p++; emptycount=10;
15937                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15938                 while (emptycount--)
15939                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15940 #endif
15941             } else if (isdigit(*p)) {
15942                 emptycount = *p++ - '0';
15943                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15944                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15945                 while (emptycount--)
15946                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15947             } else if (*p == '+' || isalpha(*p)) {
15948                 if (j >= gameInfo.boardWidth) return FALSE;
15949                 if(*p=='+') {
15950                     piece = CharToPiece(*++p);
15951                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15952                     piece = (ChessSquare) (PROMOTED piece ); p++;
15953                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15954                 } else piece = CharToPiece(*p++);
15955
15956                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15957                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15958                     piece = (ChessSquare) (PROMOTED piece);
15959                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15960                     p++;
15961                 }
15962                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15963             } else {
15964                 return FALSE;
15965             }
15966         }
15967     }
15968     while (*p == '/' || *p == ' ') p++;
15969
15970     /* [HGM] look for Crazyhouse holdings here */
15971     while(*p==' ') p++;
15972     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15973         if(*p == '[') p++;
15974         if(*p == '-' ) p++; /* empty holdings */ else {
15975             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15976             /* if we would allow FEN reading to set board size, we would   */
15977             /* have to add holdings and shift the board read so far here   */
15978             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15979                 p++;
15980                 if((int) piece >= (int) BlackPawn ) {
15981                     i = (int)piece - (int)BlackPawn;
15982                     i = PieceToNumber((ChessSquare)i);
15983                     if( i >= gameInfo.holdingsSize ) return FALSE;
15984                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15985                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15986                 } else {
15987                     i = (int)piece - (int)WhitePawn;
15988                     i = PieceToNumber((ChessSquare)i);
15989                     if( i >= gameInfo.holdingsSize ) return FALSE;
15990                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15991                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15992                 }
15993             }
15994         }
15995         if(*p == ']') p++;
15996     }
15997
15998     while(*p == ' ') p++;
15999
16000     /* Active color */
16001     c = *p++;
16002     if(appData.colorNickNames) {
16003       if( c == appData.colorNickNames[0] ) c = 'w'; else
16004       if( c == appData.colorNickNames[1] ) c = 'b';
16005     }
16006     switch (c) {
16007       case 'w':
16008         *blackPlaysFirst = FALSE;
16009         break;
16010       case 'b':
16011         *blackPlaysFirst = TRUE;
16012         break;
16013       default:
16014         return FALSE;
16015     }
16016
16017     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16018     /* return the extra info in global variiables             */
16019
16020     /* set defaults in case FEN is incomplete */
16021     board[EP_STATUS] = EP_UNKNOWN;
16022     for(i=0; i<nrCastlingRights; i++ ) {
16023         board[CASTLING][i] =
16024             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16025     }   /* assume possible unless obviously impossible */
16026     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16027     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16028     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16029                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16030     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16031     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16032     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16033                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16034     FENrulePlies = 0;
16035
16036     while(*p==' ') p++;
16037     if(nrCastlingRights) {
16038       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16039           /* castling indicator present, so default becomes no castlings */
16040           for(i=0; i<nrCastlingRights; i++ ) {
16041                  board[CASTLING][i] = NoRights;
16042           }
16043       }
16044       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16045              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16046              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16047              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16048         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16049
16050         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16051             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16052             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16053         }
16054         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16055             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16056         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16057                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16058         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16059                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16060         switch(c) {
16061           case'K':
16062               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16063               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16064               board[CASTLING][2] = whiteKingFile;
16065               break;
16066           case'Q':
16067               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16068               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16069               board[CASTLING][2] = whiteKingFile;
16070               break;
16071           case'k':
16072               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16073               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16074               board[CASTLING][5] = blackKingFile;
16075               break;
16076           case'q':
16077               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16078               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16079               board[CASTLING][5] = blackKingFile;
16080           case '-':
16081               break;
16082           default: /* FRC castlings */
16083               if(c >= 'a') { /* black rights */
16084                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16085                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16086                   if(i == BOARD_RGHT) break;
16087                   board[CASTLING][5] = i;
16088                   c -= AAA;
16089                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16090                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16091                   if(c > i)
16092                       board[CASTLING][3] = c;
16093                   else
16094                       board[CASTLING][4] = c;
16095               } else { /* white rights */
16096                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16097                     if(board[0][i] == WhiteKing) break;
16098                   if(i == BOARD_RGHT) break;
16099                   board[CASTLING][2] = i;
16100                   c -= AAA - 'a' + 'A';
16101                   if(board[0][c] >= WhiteKing) break;
16102                   if(c > i)
16103                       board[CASTLING][0] = c;
16104                   else
16105                       board[CASTLING][1] = c;
16106               }
16107         }
16108       }
16109       for(i=0; i<nrCastlingRights; i++)
16110         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16111     if (appData.debugMode) {
16112         fprintf(debugFP, "FEN castling rights:");
16113         for(i=0; i<nrCastlingRights; i++)
16114         fprintf(debugFP, " %d", board[CASTLING][i]);
16115         fprintf(debugFP, "\n");
16116     }
16117
16118       while(*p==' ') p++;
16119     }
16120
16121     /* read e.p. field in games that know e.p. capture */
16122     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16123        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16124       if(*p=='-') {
16125         p++; board[EP_STATUS] = EP_NONE;
16126       } else {
16127          char c = *p++ - AAA;
16128
16129          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16130          if(*p >= '0' && *p <='9') p++;
16131          board[EP_STATUS] = c;
16132       }
16133     }
16134
16135
16136     if(sscanf(p, "%d", &i) == 1) {
16137         FENrulePlies = i; /* 50-move ply counter */
16138         /* (The move number is still ignored)    */
16139     }
16140
16141     return TRUE;
16142 }
16143
16144 void
16145 EditPositionPasteFEN(char *fen)
16146 {
16147   if (fen != NULL) {
16148     Board initial_position;
16149
16150     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16151       DisplayError(_("Bad FEN position in clipboard"), 0);
16152       return ;
16153     } else {
16154       int savedBlackPlaysFirst = blackPlaysFirst;
16155       EditPositionEvent();
16156       blackPlaysFirst = savedBlackPlaysFirst;
16157       CopyBoard(boards[0], initial_position);
16158       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16159       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16160       DisplayBothClocks();
16161       DrawPosition(FALSE, boards[currentMove]);
16162     }
16163   }
16164 }
16165
16166 static char cseq[12] = "\\   ";
16167
16168 Boolean set_cont_sequence(char *new_seq)
16169 {
16170     int len;
16171     Boolean ret;
16172
16173     // handle bad attempts to set the sequence
16174         if (!new_seq)
16175                 return 0; // acceptable error - no debug
16176
16177     len = strlen(new_seq);
16178     ret = (len > 0) && (len < sizeof(cseq));
16179     if (ret)
16180       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16181     else if (appData.debugMode)
16182       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16183     return ret;
16184 }
16185
16186 /*
16187     reformat a source message so words don't cross the width boundary.  internal
16188     newlines are not removed.  returns the wrapped size (no null character unless
16189     included in source message).  If dest is NULL, only calculate the size required
16190     for the dest buffer.  lp argument indicats line position upon entry, and it's
16191     passed back upon exit.
16192 */
16193 int wrap(char *dest, char *src, int count, int width, int *lp)
16194 {
16195     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16196
16197     cseq_len = strlen(cseq);
16198     old_line = line = *lp;
16199     ansi = len = clen = 0;
16200
16201     for (i=0; i < count; i++)
16202     {
16203         if (src[i] == '\033')
16204             ansi = 1;
16205
16206         // if we hit the width, back up
16207         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16208         {
16209             // store i & len in case the word is too long
16210             old_i = i, old_len = len;
16211
16212             // find the end of the last word
16213             while (i && src[i] != ' ' && src[i] != '\n')
16214             {
16215                 i--;
16216                 len--;
16217             }
16218
16219             // word too long?  restore i & len before splitting it
16220             if ((old_i-i+clen) >= width)
16221             {
16222                 i = old_i;
16223                 len = old_len;
16224             }
16225
16226             // extra space?
16227             if (i && src[i-1] == ' ')
16228                 len--;
16229
16230             if (src[i] != ' ' && src[i] != '\n')
16231             {
16232                 i--;
16233                 if (len)
16234                     len--;
16235             }
16236
16237             // now append the newline and continuation sequence
16238             if (dest)
16239                 dest[len] = '\n';
16240             len++;
16241             if (dest)
16242                 strncpy(dest+len, cseq, cseq_len);
16243             len += cseq_len;
16244             line = cseq_len;
16245             clen = cseq_len;
16246             continue;
16247         }
16248
16249         if (dest)
16250             dest[len] = src[i];
16251         len++;
16252         if (!ansi)
16253             line++;
16254         if (src[i] == '\n')
16255             line = 0;
16256         if (src[i] == 'm')
16257             ansi = 0;
16258     }
16259     if (dest && appData.debugMode)
16260     {
16261         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16262             count, width, line, len, *lp);
16263         show_bytes(debugFP, src, count);
16264         fprintf(debugFP, "\ndest: ");
16265         show_bytes(debugFP, dest, len);
16266         fprintf(debugFP, "\n");
16267     }
16268     *lp = dest ? line : old_line;
16269
16270     return len;
16271 }
16272
16273 // [HGM] vari: routines for shelving variations
16274
16275 void
16276 PushInner(int firstMove, int lastMove)
16277 {
16278         int i, j, nrMoves = lastMove - firstMove;
16279
16280         // push current tail of game on stack
16281         savedResult[storedGames] = gameInfo.result;
16282         savedDetails[storedGames] = gameInfo.resultDetails;
16283         gameInfo.resultDetails = NULL;
16284         savedFirst[storedGames] = firstMove;
16285         savedLast [storedGames] = lastMove;
16286         savedFramePtr[storedGames] = framePtr;
16287         framePtr -= nrMoves; // reserve space for the boards
16288         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16289             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16290             for(j=0; j<MOVE_LEN; j++)
16291                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16292             for(j=0; j<2*MOVE_LEN; j++)
16293                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16294             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16295             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16296             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16297             pvInfoList[firstMove+i-1].depth = 0;
16298             commentList[framePtr+i] = commentList[firstMove+i];
16299             commentList[firstMove+i] = NULL;
16300         }
16301
16302         storedGames++;
16303         forwardMostMove = firstMove; // truncate game so we can start variation
16304 }
16305
16306 void
16307 PushTail(int firstMove, int lastMove)
16308 {
16309         if(appData.icsActive) { // only in local mode
16310                 forwardMostMove = currentMove; // mimic old ICS behavior
16311                 return;
16312         }
16313         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16314
16315         PushInner(firstMove, lastMove);
16316         if(storedGames == 1) GreyRevert(FALSE);
16317 }
16318
16319 void
16320 PopInner(Boolean annotate)
16321 {
16322         int i, j, nrMoves;
16323         char buf[8000], moveBuf[20];
16324
16325         storedGames--;
16326         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16327         nrMoves = savedLast[storedGames] - currentMove;
16328         if(annotate) {
16329                 int cnt = 10;
16330                 if(!WhiteOnMove(currentMove))
16331                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16332                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16333                 for(i=currentMove; i<forwardMostMove; i++) {
16334                         if(WhiteOnMove(i))
16335                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16336                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16337                         strcat(buf, moveBuf);
16338                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16339                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16340                 }
16341                 strcat(buf, ")");
16342         }
16343         for(i=1; i<=nrMoves; i++) { // copy last variation back
16344             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16345             for(j=0; j<MOVE_LEN; j++)
16346                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16347             for(j=0; j<2*MOVE_LEN; j++)
16348                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16349             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16350             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16351             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16352             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16353             commentList[currentMove+i] = commentList[framePtr+i];
16354             commentList[framePtr+i] = NULL;
16355         }
16356         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16357         framePtr = savedFramePtr[storedGames];
16358         gameInfo.result = savedResult[storedGames];
16359         if(gameInfo.resultDetails != NULL) {
16360             free(gameInfo.resultDetails);
16361       }
16362         gameInfo.resultDetails = savedDetails[storedGames];
16363         forwardMostMove = currentMove + nrMoves;
16364 }
16365
16366 Boolean
16367 PopTail(Boolean annotate)
16368 {
16369         if(appData.icsActive) return FALSE; // only in local mode
16370         if(!storedGames) return FALSE; // sanity
16371         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16372
16373         PopInner(annotate);
16374
16375         if(storedGames == 0) GreyRevert(TRUE);
16376         return TRUE;
16377 }
16378
16379 void
16380 CleanupTail()
16381 {       // remove all shelved variations
16382         int i;
16383         for(i=0; i<storedGames; i++) {
16384             if(savedDetails[i])
16385                 free(savedDetails[i]);
16386             savedDetails[i] = NULL;
16387         }
16388         for(i=framePtr; i<MAX_MOVES; i++) {
16389                 if(commentList[i]) free(commentList[i]);
16390                 commentList[i] = NULL;
16391         }
16392         framePtr = MAX_MOVES-1;
16393         storedGames = 0;
16394 }
16395
16396 void
16397 LoadVariation(int index, char *text)
16398 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16399         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16400         int level = 0, move;
16401
16402         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16403         // first find outermost bracketing variation
16404         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16405             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16406                 if(*p == '{') wait = '}'; else
16407                 if(*p == '[') wait = ']'; else
16408                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16409                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16410             }
16411             if(*p == wait) wait = NULLCHAR; // closing ]} found
16412             p++;
16413         }
16414         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16415         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16416         end[1] = NULLCHAR; // clip off comment beyond variation
16417         ToNrEvent(currentMove-1);
16418         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16419         // kludge: use ParsePV() to append variation to game
16420         move = currentMove;
16421         ParsePV(start, TRUE, TRUE);
16422         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16423         ClearPremoveHighlights();
16424         CommentPopDown();
16425         ToNrEvent(currentMove+1);
16426 }
16427