Give error popup when pairing engine fails
[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) {
7665             DisplayError(_("Invalid pairing from pairing engine"), 0);
7666             return;
7667         }
7668         pairingReceived = 1;
7669         NextMatchGame();
7670         return; // Skim the pairing messages here.
7671     }
7672
7673     cps->userError = 0;
7674
7675 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7676     /*
7677      * Kludge to ignore BEL characters
7678      */
7679     while (*message == '\007') message++;
7680
7681     /*
7682      * [HGM] engine debug message: ignore lines starting with '#' character
7683      */
7684     if(cps->debug && *message == '#') return;
7685
7686     /*
7687      * Look for book output
7688      */
7689     if (cps == &first && bookRequested) {
7690         if (message[0] == '\t' || message[0] == ' ') {
7691             /* Part of the book output is here; append it */
7692             strcat(bookOutput, message);
7693             strcat(bookOutput, "  \n");
7694             return;
7695         } else if (bookOutput[0] != NULLCHAR) {
7696             /* All of book output has arrived; display it */
7697             char *p = bookOutput;
7698             while (*p != NULLCHAR) {
7699                 if (*p == '\t') *p = ' ';
7700                 p++;
7701             }
7702             DisplayInformation(bookOutput);
7703             bookRequested = FALSE;
7704             /* Fall through to parse the current output */
7705         }
7706     }
7707
7708     /*
7709      * Look for machine move.
7710      */
7711     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7712         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7713     {
7714         /* This method is only useful on engines that support ping */
7715         if (cps->lastPing != cps->lastPong) {
7716           if (gameMode == BeginningOfGame) {
7717             /* Extra move from before last new; ignore */
7718             if (appData.debugMode) {
7719                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7720             }
7721           } else {
7722             if (appData.debugMode) {
7723                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7724                         cps->which, gameMode);
7725             }
7726
7727             SendToProgram("undo\n", cps);
7728           }
7729           return;
7730         }
7731
7732         switch (gameMode) {
7733           case BeginningOfGame:
7734             /* Extra move from before last reset; ignore */
7735             if (appData.debugMode) {
7736                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7737             }
7738             return;
7739
7740           case EndOfGame:
7741           case IcsIdle:
7742           default:
7743             /* Extra move after we tried to stop.  The mode test is
7744                not a reliable way of detecting this problem, but it's
7745                the best we can do on engines that don't support ping.
7746             */
7747             if (appData.debugMode) {
7748                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7749                         cps->which, gameMode);
7750             }
7751             SendToProgram("undo\n", cps);
7752             return;
7753
7754           case MachinePlaysWhite:
7755           case IcsPlayingWhite:
7756             machineWhite = TRUE;
7757             break;
7758
7759           case MachinePlaysBlack:
7760           case IcsPlayingBlack:
7761             machineWhite = FALSE;
7762             break;
7763
7764           case TwoMachinesPlay:
7765             machineWhite = (cps->twoMachinesColor[0] == 'w');
7766             break;
7767         }
7768         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7769             if (appData.debugMode) {
7770                 fprintf(debugFP,
7771                         "Ignoring move out of turn by %s, gameMode %d"
7772                         ", forwardMost %d\n",
7773                         cps->which, gameMode, forwardMostMove);
7774             }
7775             return;
7776         }
7777
7778     if (appData.debugMode) { int f = forwardMostMove;
7779         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7780                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7781                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7782     }
7783         if(cps->alphaRank) AlphaRank(machineMove, 4);
7784         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7785                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7786             /* Machine move could not be parsed; ignore it. */
7787           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7788                     machineMove, _(cps->which));
7789             DisplayError(buf1, 0);
7790             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7791                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7792             if (gameMode == TwoMachinesPlay) {
7793               GameEnds(machineWhite ? BlackWins : WhiteWins,
7794                        buf1, GE_XBOARD);
7795             }
7796             return;
7797         }
7798
7799         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7800         /* So we have to redo legality test with true e.p. status here,  */
7801         /* to make sure an illegal e.p. capture does not slip through,   */
7802         /* to cause a forfeit on a justified illegal-move complaint      */
7803         /* of the opponent.                                              */
7804         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7805            ChessMove moveType;
7806            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7807                              fromY, fromX, toY, toX, promoChar);
7808             if (appData.debugMode) {
7809                 int i;
7810                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7811                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7812                 fprintf(debugFP, "castling rights\n");
7813             }
7814             if(moveType == IllegalMove) {
7815               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7816                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7817                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7818                            buf1, GE_XBOARD);
7819                 return;
7820            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7821            /* [HGM] Kludge to handle engines that send FRC-style castling
7822               when they shouldn't (like TSCP-Gothic) */
7823            switch(moveType) {
7824              case WhiteASideCastleFR:
7825              case BlackASideCastleFR:
7826                toX+=2;
7827                currentMoveString[2]++;
7828                break;
7829              case WhiteHSideCastleFR:
7830              case BlackHSideCastleFR:
7831                toX--;
7832                currentMoveString[2]--;
7833                break;
7834              default: ; // nothing to do, but suppresses warning of pedantic compilers
7835            }
7836         }
7837         hintRequested = FALSE;
7838         lastHint[0] = NULLCHAR;
7839         bookRequested = FALSE;
7840         /* Program may be pondering now */
7841         cps->maybeThinking = TRUE;
7842         if (cps->sendTime == 2) cps->sendTime = 1;
7843         if (cps->offeredDraw) cps->offeredDraw--;
7844
7845         /* [AS] Save move info*/
7846         pvInfoList[ forwardMostMove ].score = programStats.score;
7847         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7848         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7849
7850         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7851
7852         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7853         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7854             int count = 0;
7855
7856             while( count < adjudicateLossPlies ) {
7857                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7858
7859                 if( count & 1 ) {
7860                     score = -score; /* Flip score for winning side */
7861                 }
7862
7863                 if( score > adjudicateLossThreshold ) {
7864                     break;
7865                 }
7866
7867                 count++;
7868             }
7869
7870             if( count >= adjudicateLossPlies ) {
7871                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7872
7873                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7874                     "Xboard adjudication",
7875                     GE_XBOARD );
7876
7877                 return;
7878             }
7879         }
7880
7881         if(Adjudicate(cps)) {
7882             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7883             return; // [HGM] adjudicate: for all automatic game ends
7884         }
7885
7886 #if ZIPPY
7887         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7888             first.initDone) {
7889           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7890                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7891                 SendToICS("draw ");
7892                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7893           }
7894           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7895           ics_user_moved = 1;
7896           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7897                 char buf[3*MSG_SIZ];
7898
7899                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7900                         programStats.score / 100.,
7901                         programStats.depth,
7902                         programStats.time / 100.,
7903                         (unsigned int)programStats.nodes,
7904                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7905                         programStats.movelist);
7906                 SendToICS(buf);
7907 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7908           }
7909         }
7910 #endif
7911
7912         /* [AS] Clear stats for next move */
7913         ClearProgramStats();
7914         thinkOutput[0] = NULLCHAR;
7915         hiddenThinkOutputState = 0;
7916
7917         bookHit = NULL;
7918         if (gameMode == TwoMachinesPlay) {
7919             /* [HGM] relaying draw offers moved to after reception of move */
7920             /* and interpreting offer as claim if it brings draw condition */
7921             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7922                 SendToProgram("draw\n", cps->other);
7923             }
7924             if (cps->other->sendTime) {
7925                 SendTimeRemaining(cps->other,
7926                                   cps->other->twoMachinesColor[0] == 'w');
7927             }
7928             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7929             if (firstMove && !bookHit) {
7930                 firstMove = FALSE;
7931                 if (cps->other->useColors) {
7932                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7933                 }
7934                 SendToProgram("go\n", cps->other);
7935             }
7936             cps->other->maybeThinking = TRUE;
7937         }
7938
7939         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7940
7941         if (!pausing && appData.ringBellAfterMoves) {
7942             RingBell();
7943         }
7944
7945         /*
7946          * Reenable menu items that were disabled while
7947          * machine was thinking
7948          */
7949         if (gameMode != TwoMachinesPlay)
7950             SetUserThinkingEnables();
7951
7952         // [HGM] book: after book hit opponent has received move and is now in force mode
7953         // force the book reply into it, and then fake that it outputted this move by jumping
7954         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7955         if(bookHit) {
7956                 static char bookMove[MSG_SIZ]; // a bit generous?
7957
7958                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7959                 strcat(bookMove, bookHit);
7960                 message = bookMove;
7961                 cps = cps->other;
7962                 programStats.nodes = programStats.depth = programStats.time =
7963                 programStats.score = programStats.got_only_move = 0;
7964                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7965
7966                 if(cps->lastPing != cps->lastPong) {
7967                     savedMessage = message; // args for deferred call
7968                     savedState = cps;
7969                     ScheduleDelayedEvent(DeferredBookMove, 10);
7970                     return;
7971                 }
7972                 goto FakeBookMove;
7973         }
7974
7975         return;
7976     }
7977
7978     /* Set special modes for chess engines.  Later something general
7979      *  could be added here; for now there is just one kludge feature,
7980      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7981      *  when "xboard" is given as an interactive command.
7982      */
7983     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7984         cps->useSigint = FALSE;
7985         cps->useSigterm = FALSE;
7986     }
7987     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7988       ParseFeatures(message+8, cps);
7989       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7990     }
7991
7992     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7993       int dummy, s=6; char buf[MSG_SIZ];
7994       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7995       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7996       ParseFEN(boards[0], &dummy, message+s);
7997       DrawPosition(TRUE, boards[0]);
7998       startedFromSetupPosition = TRUE;
7999       return;
8000     }
8001     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8002      * want this, I was asked to put it in, and obliged.
8003      */
8004     if (!strncmp(message, "setboard ", 9)) {
8005         Board initial_position;
8006
8007         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8008
8009         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8010             DisplayError(_("Bad FEN received from engine"), 0);
8011             return ;
8012         } else {
8013            Reset(TRUE, FALSE);
8014            CopyBoard(boards[0], initial_position);
8015            initialRulePlies = FENrulePlies;
8016            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8017            else gameMode = MachinePlaysBlack;
8018            DrawPosition(FALSE, boards[currentMove]);
8019         }
8020         return;
8021     }
8022
8023     /*
8024      * Look for communication commands
8025      */
8026     if (!strncmp(message, "telluser ", 9)) {
8027         if(message[9] == '\\' && message[10] == '\\')
8028             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8029         DisplayNote(message + 9);
8030         return;
8031     }
8032     if (!strncmp(message, "tellusererror ", 14)) {
8033         cps->userError = 1;
8034         if(message[14] == '\\' && message[15] == '\\')
8035             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8036         DisplayError(message + 14, 0);
8037         return;
8038     }
8039     if (!strncmp(message, "tellopponent ", 13)) {
8040       if (appData.icsActive) {
8041         if (loggedOn) {
8042           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8043           SendToICS(buf1);
8044         }
8045       } else {
8046         DisplayNote(message + 13);
8047       }
8048       return;
8049     }
8050     if (!strncmp(message, "tellothers ", 11)) {
8051       if (appData.icsActive) {
8052         if (loggedOn) {
8053           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8054           SendToICS(buf1);
8055         }
8056       }
8057       return;
8058     }
8059     if (!strncmp(message, "tellall ", 8)) {
8060       if (appData.icsActive) {
8061         if (loggedOn) {
8062           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8063           SendToICS(buf1);
8064         }
8065       } else {
8066         DisplayNote(message + 8);
8067       }
8068       return;
8069     }
8070     if (strncmp(message, "warning", 7) == 0) {
8071         /* Undocumented feature, use tellusererror in new code */
8072         DisplayError(message, 0);
8073         return;
8074     }
8075     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8076         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8077         strcat(realname, " query");
8078         AskQuestion(realname, buf2, buf1, cps->pr);
8079         return;
8080     }
8081     /* Commands from the engine directly to ICS.  We don't allow these to be
8082      *  sent until we are logged on. Crafty kibitzes have been known to
8083      *  interfere with the login process.
8084      */
8085     if (loggedOn) {
8086         if (!strncmp(message, "tellics ", 8)) {
8087             SendToICS(message + 8);
8088             SendToICS("\n");
8089             return;
8090         }
8091         if (!strncmp(message, "tellicsnoalias ", 15)) {
8092             SendToICS(ics_prefix);
8093             SendToICS(message + 15);
8094             SendToICS("\n");
8095             return;
8096         }
8097         /* The following are for backward compatibility only */
8098         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8099             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8100             SendToICS(ics_prefix);
8101             SendToICS(message);
8102             SendToICS("\n");
8103             return;
8104         }
8105     }
8106     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8107         return;
8108     }
8109     /*
8110      * If the move is illegal, cancel it and redraw the board.
8111      * Also deal with other error cases.  Matching is rather loose
8112      * here to accommodate engines written before the spec.
8113      */
8114     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8115         strncmp(message, "Error", 5) == 0) {
8116         if (StrStr(message, "name") ||
8117             StrStr(message, "rating") || StrStr(message, "?") ||
8118             StrStr(message, "result") || StrStr(message, "board") ||
8119             StrStr(message, "bk") || StrStr(message, "computer") ||
8120             StrStr(message, "variant") || StrStr(message, "hint") ||
8121             StrStr(message, "random") || StrStr(message, "depth") ||
8122             StrStr(message, "accepted")) {
8123             return;
8124         }
8125         if (StrStr(message, "protover")) {
8126           /* Program is responding to input, so it's apparently done
8127              initializing, and this error message indicates it is
8128              protocol version 1.  So we don't need to wait any longer
8129              for it to initialize and send feature commands. */
8130           FeatureDone(cps, 1);
8131           cps->protocolVersion = 1;
8132           return;
8133         }
8134         cps->maybeThinking = FALSE;
8135
8136         if (StrStr(message, "draw")) {
8137             /* Program doesn't have "draw" command */
8138             cps->sendDrawOffers = 0;
8139             return;
8140         }
8141         if (cps->sendTime != 1 &&
8142             (StrStr(message, "time") || StrStr(message, "otim"))) {
8143           /* Program apparently doesn't have "time" or "otim" command */
8144           cps->sendTime = 0;
8145           return;
8146         }
8147         if (StrStr(message, "analyze")) {
8148             cps->analysisSupport = FALSE;
8149             cps->analyzing = FALSE;
8150             Reset(FALSE, TRUE);
8151             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8152             DisplayError(buf2, 0);
8153             return;
8154         }
8155         if (StrStr(message, "(no matching move)st")) {
8156           /* Special kludge for GNU Chess 4 only */
8157           cps->stKludge = TRUE;
8158           SendTimeControl(cps, movesPerSession, timeControl,
8159                           timeIncrement, appData.searchDepth,
8160                           searchTime);
8161           return;
8162         }
8163         if (StrStr(message, "(no matching move)sd")) {
8164           /* Special kludge for GNU Chess 4 only */
8165           cps->sdKludge = TRUE;
8166           SendTimeControl(cps, movesPerSession, timeControl,
8167                           timeIncrement, appData.searchDepth,
8168                           searchTime);
8169           return;
8170         }
8171         if (!StrStr(message, "llegal")) {
8172             return;
8173         }
8174         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8175             gameMode == IcsIdle) return;
8176         if (forwardMostMove <= backwardMostMove) return;
8177         if (pausing) PauseEvent();
8178       if(appData.forceIllegal) {
8179             // [HGM] illegal: machine refused move; force position after move into it
8180           SendToProgram("force\n", cps);
8181           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8182                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8183                 // when black is to move, while there might be nothing on a2 or black
8184                 // might already have the move. So send the board as if white has the move.
8185                 // But first we must change the stm of the engine, as it refused the last move
8186                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8187                 if(WhiteOnMove(forwardMostMove)) {
8188                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8189                     SendBoard(cps, forwardMostMove); // kludgeless board
8190                 } else {
8191                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8192                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8193                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8194                 }
8195           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8196             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8197                  gameMode == TwoMachinesPlay)
8198               SendToProgram("go\n", cps);
8199             return;
8200       } else
8201         if (gameMode == PlayFromGameFile) {
8202             /* Stop reading this game file */
8203             gameMode = EditGame;
8204             ModeHighlight();
8205         }
8206         /* [HGM] illegal-move claim should forfeit game when Xboard */
8207         /* only passes fully legal moves                            */
8208         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8209             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8210                                 "False illegal-move claim", GE_XBOARD );
8211             return; // do not take back move we tested as valid
8212         }
8213         currentMove = forwardMostMove-1;
8214         DisplayMove(currentMove-1); /* before DisplayMoveError */
8215         SwitchClocks(forwardMostMove-1); // [HGM] race
8216         DisplayBothClocks();
8217         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8218                 parseList[currentMove], _(cps->which));
8219         DisplayMoveError(buf1);
8220         DrawPosition(FALSE, boards[currentMove]);
8221         return;
8222     }
8223     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8224         /* Program has a broken "time" command that
8225            outputs a string not ending in newline.
8226            Don't use it. */
8227         cps->sendTime = 0;
8228     }
8229
8230     /*
8231      * If chess program startup fails, exit with an error message.
8232      * Attempts to recover here are futile.
8233      */
8234     if ((StrStr(message, "unknown host") != NULL)
8235         || (StrStr(message, "No remote directory") != NULL)
8236         || (StrStr(message, "not found") != NULL)
8237         || (StrStr(message, "No such file") != NULL)
8238         || (StrStr(message, "can't alloc") != NULL)
8239         || (StrStr(message, "Permission denied") != NULL)) {
8240
8241         cps->maybeThinking = FALSE;
8242         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8243                 _(cps->which), cps->program, cps->host, message);
8244         RemoveInputSource(cps->isr);
8245         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8246             if(cps == &first) appData.noChessProgram = TRUE;
8247             DisplayError(buf1, 0);
8248         }
8249         return;
8250     }
8251
8252     /*
8253      * Look for hint output
8254      */
8255     if (sscanf(message, "Hint: %s", buf1) == 1) {
8256         if (cps == &first && hintRequested) {
8257             hintRequested = FALSE;
8258             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8259                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8260                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8261                                     PosFlags(forwardMostMove),
8262                                     fromY, fromX, toY, toX, promoChar, buf1);
8263                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8264                 DisplayInformation(buf2);
8265             } else {
8266                 /* Hint move could not be parsed!? */
8267               snprintf(buf2, sizeof(buf2),
8268                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8269                         buf1, _(cps->which));
8270                 DisplayError(buf2, 0);
8271             }
8272         } else {
8273           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8274         }
8275         return;
8276     }
8277
8278     /*
8279      * Ignore other messages if game is not in progress
8280      */
8281     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8282         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8283
8284     /*
8285      * look for win, lose, draw, or draw offer
8286      */
8287     if (strncmp(message, "1-0", 3) == 0) {
8288         char *p, *q, *r = "";
8289         p = strchr(message, '{');
8290         if (p) {
8291             q = strchr(p, '}');
8292             if (q) {
8293                 *q = NULLCHAR;
8294                 r = p + 1;
8295             }
8296         }
8297         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8298         return;
8299     } else if (strncmp(message, "0-1", 3) == 0) {
8300         char *p, *q, *r = "";
8301         p = strchr(message, '{');
8302         if (p) {
8303             q = strchr(p, '}');
8304             if (q) {
8305                 *q = NULLCHAR;
8306                 r = p + 1;
8307             }
8308         }
8309         /* Kludge for Arasan 4.1 bug */
8310         if (strcmp(r, "Black resigns") == 0) {
8311             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8312             return;
8313         }
8314         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8315         return;
8316     } else if (strncmp(message, "1/2", 3) == 0) {
8317         char *p, *q, *r = "";
8318         p = strchr(message, '{');
8319         if (p) {
8320             q = strchr(p, '}');
8321             if (q) {
8322                 *q = NULLCHAR;
8323                 r = p + 1;
8324             }
8325         }
8326
8327         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8328         return;
8329
8330     } else if (strncmp(message, "White resign", 12) == 0) {
8331         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8332         return;
8333     } else if (strncmp(message, "Black resign", 12) == 0) {
8334         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8335         return;
8336     } else if (strncmp(message, "White matches", 13) == 0 ||
8337                strncmp(message, "Black matches", 13) == 0   ) {
8338         /* [HGM] ignore GNUShogi noises */
8339         return;
8340     } else if (strncmp(message, "White", 5) == 0 &&
8341                message[5] != '(' &&
8342                StrStr(message, "Black") == NULL) {
8343         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8344         return;
8345     } else if (strncmp(message, "Black", 5) == 0 &&
8346                message[5] != '(') {
8347         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8348         return;
8349     } else if (strcmp(message, "resign") == 0 ||
8350                strcmp(message, "computer resigns") == 0) {
8351         switch (gameMode) {
8352           case MachinePlaysBlack:
8353           case IcsPlayingBlack:
8354             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8355             break;
8356           case MachinePlaysWhite:
8357           case IcsPlayingWhite:
8358             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8359             break;
8360           case TwoMachinesPlay:
8361             if (cps->twoMachinesColor[0] == 'w')
8362               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8363             else
8364               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8365             break;
8366           default:
8367             /* can't happen */
8368             break;
8369         }
8370         return;
8371     } else if (strncmp(message, "opponent mates", 14) == 0) {
8372         switch (gameMode) {
8373           case MachinePlaysBlack:
8374           case IcsPlayingBlack:
8375             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8376             break;
8377           case MachinePlaysWhite:
8378           case IcsPlayingWhite:
8379             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8380             break;
8381           case TwoMachinesPlay:
8382             if (cps->twoMachinesColor[0] == 'w')
8383               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8384             else
8385               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8386             break;
8387           default:
8388             /* can't happen */
8389             break;
8390         }
8391         return;
8392     } else if (strncmp(message, "computer mates", 14) == 0) {
8393         switch (gameMode) {
8394           case MachinePlaysBlack:
8395           case IcsPlayingBlack:
8396             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8397             break;
8398           case MachinePlaysWhite:
8399           case IcsPlayingWhite:
8400             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8401             break;
8402           case TwoMachinesPlay:
8403             if (cps->twoMachinesColor[0] == 'w')
8404               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8405             else
8406               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8407             break;
8408           default:
8409             /* can't happen */
8410             break;
8411         }
8412         return;
8413     } else if (strncmp(message, "checkmate", 9) == 0) {
8414         if (WhiteOnMove(forwardMostMove)) {
8415             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8416         } else {
8417             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8418         }
8419         return;
8420     } else if (strstr(message, "Draw") != NULL ||
8421                strstr(message, "game is a draw") != NULL) {
8422         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8423         return;
8424     } else if (strstr(message, "offer") != NULL &&
8425                strstr(message, "draw") != NULL) {
8426 #if ZIPPY
8427         if (appData.zippyPlay && first.initDone) {
8428             /* Relay offer to ICS */
8429             SendToICS(ics_prefix);
8430             SendToICS("draw\n");
8431         }
8432 #endif
8433         cps->offeredDraw = 2; /* valid until this engine moves twice */
8434         if (gameMode == TwoMachinesPlay) {
8435             if (cps->other->offeredDraw) {
8436                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8437             /* [HGM] in two-machine mode we delay relaying draw offer      */
8438             /* until after we also have move, to see if it is really claim */
8439             }
8440         } else if (gameMode == MachinePlaysWhite ||
8441                    gameMode == MachinePlaysBlack) {
8442           if (userOfferedDraw) {
8443             DisplayInformation(_("Machine accepts your draw offer"));
8444             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8445           } else {
8446             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8447           }
8448         }
8449     }
8450
8451
8452     /*
8453      * Look for thinking output
8454      */
8455     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8456           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8457                                 ) {
8458         int plylev, mvleft, mvtot, curscore, time;
8459         char mvname[MOVE_LEN];
8460         u64 nodes; // [DM]
8461         char plyext;
8462         int ignore = FALSE;
8463         int prefixHint = FALSE;
8464         mvname[0] = NULLCHAR;
8465
8466         switch (gameMode) {
8467           case MachinePlaysBlack:
8468           case IcsPlayingBlack:
8469             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8470             break;
8471           case MachinePlaysWhite:
8472           case IcsPlayingWhite:
8473             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8474             break;
8475           case AnalyzeMode:
8476           case AnalyzeFile:
8477             break;
8478           case IcsObserving: /* [DM] icsEngineAnalyze */
8479             if (!appData.icsEngineAnalyze) ignore = TRUE;
8480             break;
8481           case TwoMachinesPlay:
8482             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8483                 ignore = TRUE;
8484             }
8485             break;
8486           default:
8487             ignore = TRUE;
8488             break;
8489         }
8490
8491         if (!ignore) {
8492             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8493             buf1[0] = NULLCHAR;
8494             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8495                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8496
8497                 if (plyext != ' ' && plyext != '\t') {
8498                     time *= 100;
8499                 }
8500
8501                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8502                 if( cps->scoreIsAbsolute &&
8503                     ( gameMode == MachinePlaysBlack ||
8504                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8505                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8506                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8507                      !WhiteOnMove(currentMove)
8508                     ) )
8509                 {
8510                     curscore = -curscore;
8511                 }
8512
8513
8514                 tempStats.depth = plylev;
8515                 tempStats.nodes = nodes;
8516                 tempStats.time = time;
8517                 tempStats.score = curscore;
8518                 tempStats.got_only_move = 0;
8519
8520                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8521                         int ticklen;
8522
8523                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8524                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8525                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8526                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8527                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8528                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8529                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8530                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8531                 }
8532
8533                 /* Buffer overflow protection */
8534                 if (buf1[0] != NULLCHAR) {
8535                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8536                         && appData.debugMode) {
8537                         fprintf(debugFP,
8538                                 "PV is too long; using the first %u bytes.\n",
8539                                 (unsigned) sizeof(tempStats.movelist) - 1);
8540                     }
8541
8542                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8543                 } else {
8544                     sprintf(tempStats.movelist, " no PV\n");
8545                 }
8546
8547                 if (tempStats.seen_stat) {
8548                     tempStats.ok_to_send = 1;
8549                 }
8550
8551                 if (strchr(tempStats.movelist, '(') != NULL) {
8552                     tempStats.line_is_book = 1;
8553                     tempStats.nr_moves = 0;
8554                     tempStats.moves_left = 0;
8555                 } else {
8556                     tempStats.line_is_book = 0;
8557                 }
8558
8559                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8560                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8561
8562                 SendProgramStatsToFrontend( cps, &tempStats );
8563
8564                 /*
8565                     [AS] Protect the thinkOutput buffer from overflow... this
8566                     is only useful if buf1 hasn't overflowed first!
8567                 */
8568                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8569                          plylev,
8570                          (gameMode == TwoMachinesPlay ?
8571                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8572                          ((double) curscore) / 100.0,
8573                          prefixHint ? lastHint : "",
8574                          prefixHint ? " " : "" );
8575
8576                 if( buf1[0] != NULLCHAR ) {
8577                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8578
8579                     if( strlen(buf1) > max_len ) {
8580                         if( appData.debugMode) {
8581                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8582                         }
8583                         buf1[max_len+1] = '\0';
8584                     }
8585
8586                     strcat( thinkOutput, buf1 );
8587                 }
8588
8589                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8590                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8591                     DisplayMove(currentMove - 1);
8592                 }
8593                 return;
8594
8595             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8596                 /* crafty (9.25+) says "(only move) <move>"
8597                  * if there is only 1 legal move
8598                  */
8599                 sscanf(p, "(only move) %s", buf1);
8600                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8601                 sprintf(programStats.movelist, "%s (only move)", buf1);
8602                 programStats.depth = 1;
8603                 programStats.nr_moves = 1;
8604                 programStats.moves_left = 1;
8605                 programStats.nodes = 1;
8606                 programStats.time = 1;
8607                 programStats.got_only_move = 1;
8608
8609                 /* Not really, but we also use this member to
8610                    mean "line isn't going to change" (Crafty
8611                    isn't searching, so stats won't change) */
8612                 programStats.line_is_book = 1;
8613
8614                 SendProgramStatsToFrontend( cps, &programStats );
8615
8616                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8617                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8618                     DisplayMove(currentMove - 1);
8619                 }
8620                 return;
8621             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8622                               &time, &nodes, &plylev, &mvleft,
8623                               &mvtot, mvname) >= 5) {
8624                 /* The stat01: line is from Crafty (9.29+) in response
8625                    to the "." command */
8626                 programStats.seen_stat = 1;
8627                 cps->maybeThinking = TRUE;
8628
8629                 if (programStats.got_only_move || !appData.periodicUpdates)
8630                   return;
8631
8632                 programStats.depth = plylev;
8633                 programStats.time = time;
8634                 programStats.nodes = nodes;
8635                 programStats.moves_left = mvleft;
8636                 programStats.nr_moves = mvtot;
8637                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8638                 programStats.ok_to_send = 1;
8639                 programStats.movelist[0] = '\0';
8640
8641                 SendProgramStatsToFrontend( cps, &programStats );
8642
8643                 return;
8644
8645             } else if (strncmp(message,"++",2) == 0) {
8646                 /* Crafty 9.29+ outputs this */
8647                 programStats.got_fail = 2;
8648                 return;
8649
8650             } else if (strncmp(message,"--",2) == 0) {
8651                 /* Crafty 9.29+ outputs this */
8652                 programStats.got_fail = 1;
8653                 return;
8654
8655             } else if (thinkOutput[0] != NULLCHAR &&
8656                        strncmp(message, "    ", 4) == 0) {
8657                 unsigned message_len;
8658
8659                 p = message;
8660                 while (*p && *p == ' ') p++;
8661
8662                 message_len = strlen( p );
8663
8664                 /* [AS] Avoid buffer overflow */
8665                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8666                     strcat(thinkOutput, " ");
8667                     strcat(thinkOutput, p);
8668                 }
8669
8670                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8671                     strcat(programStats.movelist, " ");
8672                     strcat(programStats.movelist, p);
8673                 }
8674
8675                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8676                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8677                     DisplayMove(currentMove - 1);
8678                 }
8679                 return;
8680             }
8681         }
8682         else {
8683             buf1[0] = NULLCHAR;
8684
8685             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8686                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8687             {
8688                 ChessProgramStats cpstats;
8689
8690                 if (plyext != ' ' && plyext != '\t') {
8691                     time *= 100;
8692                 }
8693
8694                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8695                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8696                     curscore = -curscore;
8697                 }
8698
8699                 cpstats.depth = plylev;
8700                 cpstats.nodes = nodes;
8701                 cpstats.time = time;
8702                 cpstats.score = curscore;
8703                 cpstats.got_only_move = 0;
8704                 cpstats.movelist[0] = '\0';
8705
8706                 if (buf1[0] != NULLCHAR) {
8707                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8708                 }
8709
8710                 cpstats.ok_to_send = 0;
8711                 cpstats.line_is_book = 0;
8712                 cpstats.nr_moves = 0;
8713                 cpstats.moves_left = 0;
8714
8715                 SendProgramStatsToFrontend( cps, &cpstats );
8716             }
8717         }
8718     }
8719 }
8720
8721
8722 /* Parse a game score from the character string "game", and
8723    record it as the history of the current game.  The game
8724    score is NOT assumed to start from the standard position.
8725    The display is not updated in any way.
8726    */
8727 void
8728 ParseGameHistory(game)
8729      char *game;
8730 {
8731     ChessMove moveType;
8732     int fromX, fromY, toX, toY, boardIndex;
8733     char promoChar;
8734     char *p, *q;
8735     char buf[MSG_SIZ];
8736
8737     if (appData.debugMode)
8738       fprintf(debugFP, "Parsing game history: %s\n", game);
8739
8740     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8741     gameInfo.site = StrSave(appData.icsHost);
8742     gameInfo.date = PGNDate();
8743     gameInfo.round = StrSave("-");
8744
8745     /* Parse out names of players */
8746     while (*game == ' ') game++;
8747     p = buf;
8748     while (*game != ' ') *p++ = *game++;
8749     *p = NULLCHAR;
8750     gameInfo.white = StrSave(buf);
8751     while (*game == ' ') game++;
8752     p = buf;
8753     while (*game != ' ' && *game != '\n') *p++ = *game++;
8754     *p = NULLCHAR;
8755     gameInfo.black = StrSave(buf);
8756
8757     /* Parse moves */
8758     boardIndex = blackPlaysFirst ? 1 : 0;
8759     yynewstr(game);
8760     for (;;) {
8761         yyboardindex = boardIndex;
8762         moveType = (ChessMove) Myylex();
8763         switch (moveType) {
8764           case IllegalMove:             /* maybe suicide chess, etc. */
8765   if (appData.debugMode) {
8766     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8767     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8768     setbuf(debugFP, NULL);
8769   }
8770           case WhitePromotion:
8771           case BlackPromotion:
8772           case WhiteNonPromotion:
8773           case BlackNonPromotion:
8774           case NormalMove:
8775           case WhiteCapturesEnPassant:
8776           case BlackCapturesEnPassant:
8777           case WhiteKingSideCastle:
8778           case WhiteQueenSideCastle:
8779           case BlackKingSideCastle:
8780           case BlackQueenSideCastle:
8781           case WhiteKingSideCastleWild:
8782           case WhiteQueenSideCastleWild:
8783           case BlackKingSideCastleWild:
8784           case BlackQueenSideCastleWild:
8785           /* PUSH Fabien */
8786           case WhiteHSideCastleFR:
8787           case WhiteASideCastleFR:
8788           case BlackHSideCastleFR:
8789           case BlackASideCastleFR:
8790           /* POP Fabien */
8791             fromX = currentMoveString[0] - AAA;
8792             fromY = currentMoveString[1] - ONE;
8793             toX = currentMoveString[2] - AAA;
8794             toY = currentMoveString[3] - ONE;
8795             promoChar = currentMoveString[4];
8796             break;
8797           case WhiteDrop:
8798           case BlackDrop:
8799             fromX = moveType == WhiteDrop ?
8800               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8801             (int) CharToPiece(ToLower(currentMoveString[0]));
8802             fromY = DROP_RANK;
8803             toX = currentMoveString[2] - AAA;
8804             toY = currentMoveString[3] - ONE;
8805             promoChar = NULLCHAR;
8806             break;
8807           case AmbiguousMove:
8808             /* bug? */
8809             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8810   if (appData.debugMode) {
8811     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8812     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8813     setbuf(debugFP, NULL);
8814   }
8815             DisplayError(buf, 0);
8816             return;
8817           case ImpossibleMove:
8818             /* bug? */
8819             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8820   if (appData.debugMode) {
8821     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8822     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8823     setbuf(debugFP, NULL);
8824   }
8825             DisplayError(buf, 0);
8826             return;
8827           case EndOfFile:
8828             if (boardIndex < backwardMostMove) {
8829                 /* Oops, gap.  How did that happen? */
8830                 DisplayError(_("Gap in move list"), 0);
8831                 return;
8832             }
8833             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8834             if (boardIndex > forwardMostMove) {
8835                 forwardMostMove = boardIndex;
8836             }
8837             return;
8838           case ElapsedTime:
8839             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8840                 strcat(parseList[boardIndex-1], " ");
8841                 strcat(parseList[boardIndex-1], yy_text);
8842             }
8843             continue;
8844           case Comment:
8845           case PGNTag:
8846           case NAG:
8847           default:
8848             /* ignore */
8849             continue;
8850           case WhiteWins:
8851           case BlackWins:
8852           case GameIsDrawn:
8853           case GameUnfinished:
8854             if (gameMode == IcsExamining) {
8855                 if (boardIndex < backwardMostMove) {
8856                     /* Oops, gap.  How did that happen? */
8857                     return;
8858                 }
8859                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8860                 return;
8861             }
8862             gameInfo.result = moveType;
8863             p = strchr(yy_text, '{');
8864             if (p == NULL) p = strchr(yy_text, '(');
8865             if (p == NULL) {
8866                 p = yy_text;
8867                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8868             } else {
8869                 q = strchr(p, *p == '{' ? '}' : ')');
8870                 if (q != NULL) *q = NULLCHAR;
8871                 p++;
8872             }
8873             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8874             gameInfo.resultDetails = StrSave(p);
8875             continue;
8876         }
8877         if (boardIndex >= forwardMostMove &&
8878             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8879             backwardMostMove = blackPlaysFirst ? 1 : 0;
8880             return;
8881         }
8882         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8883                                  fromY, fromX, toY, toX, promoChar,
8884                                  parseList[boardIndex]);
8885         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8886         /* currentMoveString is set as a side-effect of yylex */
8887         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8888         strcat(moveList[boardIndex], "\n");
8889         boardIndex++;
8890         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8891         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8892           case MT_NONE:
8893           case MT_STALEMATE:
8894           default:
8895             break;
8896           case MT_CHECK:
8897             if(gameInfo.variant != VariantShogi)
8898                 strcat(parseList[boardIndex - 1], "+");
8899             break;
8900           case MT_CHECKMATE:
8901           case MT_STAINMATE:
8902             strcat(parseList[boardIndex - 1], "#");
8903             break;
8904         }
8905     }
8906 }
8907
8908
8909 /* Apply a move to the given board  */
8910 void
8911 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8912      int fromX, fromY, toX, toY;
8913      int promoChar;
8914      Board board;
8915 {
8916   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8917   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8918
8919     /* [HGM] compute & store e.p. status and castling rights for new position */
8920     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8921
8922       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8923       oldEP = (signed char)board[EP_STATUS];
8924       board[EP_STATUS] = EP_NONE;
8925
8926       if( board[toY][toX] != EmptySquare )
8927            board[EP_STATUS] = EP_CAPTURE;
8928
8929   if (fromY == DROP_RANK) {
8930         /* must be first */
8931         piece = board[toY][toX] = (ChessSquare) fromX;
8932   } else {
8933       int i;
8934
8935       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8936            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8937                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8938       } else
8939       if( board[fromY][fromX] == WhitePawn ) {
8940            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8941                board[EP_STATUS] = EP_PAWN_MOVE;
8942            if( toY-fromY==2) {
8943                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8944                         gameInfo.variant != VariantBerolina || toX < fromX)
8945                       board[EP_STATUS] = toX | berolina;
8946                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8947                         gameInfo.variant != VariantBerolina || toX > fromX)
8948                       board[EP_STATUS] = toX;
8949            }
8950       } else
8951       if( board[fromY][fromX] == BlackPawn ) {
8952            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8953                board[EP_STATUS] = EP_PAWN_MOVE;
8954            if( toY-fromY== -2) {
8955                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8956                         gameInfo.variant != VariantBerolina || toX < fromX)
8957                       board[EP_STATUS] = toX | berolina;
8958                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8959                         gameInfo.variant != VariantBerolina || toX > fromX)
8960                       board[EP_STATUS] = toX;
8961            }
8962        }
8963
8964        for(i=0; i<nrCastlingRights; i++) {
8965            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8966               board[CASTLING][i] == toX   && castlingRank[i] == toY
8967              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8968        }
8969
8970      if (fromX == toX && fromY == toY) return;
8971
8972      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8973      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8974      if(gameInfo.variant == VariantKnightmate)
8975          king += (int) WhiteUnicorn - (int) WhiteKing;
8976
8977     /* Code added by Tord: */
8978     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8979     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8980         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8981       board[fromY][fromX] = EmptySquare;
8982       board[toY][toX] = EmptySquare;
8983       if((toX > fromX) != (piece == WhiteRook)) {
8984         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8985       } else {
8986         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8987       }
8988     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8989                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8990       board[fromY][fromX] = EmptySquare;
8991       board[toY][toX] = EmptySquare;
8992       if((toX > fromX) != (piece == BlackRook)) {
8993         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8994       } else {
8995         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8996       }
8997     /* End of code added by Tord */
8998
8999     } else if (board[fromY][fromX] == king
9000         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9001         && toY == fromY && toX > fromX+1) {
9002         board[fromY][fromX] = EmptySquare;
9003         board[toY][toX] = king;
9004         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9005         board[fromY][BOARD_RGHT-1] = EmptySquare;
9006     } else if (board[fromY][fromX] == king
9007         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9008                && toY == fromY && toX < fromX-1) {
9009         board[fromY][fromX] = EmptySquare;
9010         board[toY][toX] = king;
9011         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9012         board[fromY][BOARD_LEFT] = EmptySquare;
9013     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9014                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9015                && toY >= BOARD_HEIGHT-promoRank
9016                ) {
9017         /* white pawn promotion */
9018         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9019         if (board[toY][toX] == EmptySquare) {
9020             board[toY][toX] = WhiteQueen;
9021         }
9022         if(gameInfo.variant==VariantBughouse ||
9023            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9024             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9025         board[fromY][fromX] = EmptySquare;
9026     } else if ((fromY == BOARD_HEIGHT-4)
9027                && (toX != fromX)
9028                && gameInfo.variant != VariantXiangqi
9029                && gameInfo.variant != VariantBerolina
9030                && (board[fromY][fromX] == WhitePawn)
9031                && (board[toY][toX] == EmptySquare)) {
9032         board[fromY][fromX] = EmptySquare;
9033         board[toY][toX] = WhitePawn;
9034         captured = board[toY - 1][toX];
9035         board[toY - 1][toX] = EmptySquare;
9036     } else if ((fromY == BOARD_HEIGHT-4)
9037                && (toX == fromX)
9038                && gameInfo.variant == VariantBerolina
9039                && (board[fromY][fromX] == WhitePawn)
9040                && (board[toY][toX] == EmptySquare)) {
9041         board[fromY][fromX] = EmptySquare;
9042         board[toY][toX] = WhitePawn;
9043         if(oldEP & EP_BEROLIN_A) {
9044                 captured = board[fromY][fromX-1];
9045                 board[fromY][fromX-1] = EmptySquare;
9046         }else{  captured = board[fromY][fromX+1];
9047                 board[fromY][fromX+1] = EmptySquare;
9048         }
9049     } else if (board[fromY][fromX] == king
9050         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9051                && toY == fromY && toX > fromX+1) {
9052         board[fromY][fromX] = EmptySquare;
9053         board[toY][toX] = king;
9054         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9055         board[fromY][BOARD_RGHT-1] = EmptySquare;
9056     } else if (board[fromY][fromX] == king
9057         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9058                && toY == fromY && toX < fromX-1) {
9059         board[fromY][fromX] = EmptySquare;
9060         board[toY][toX] = king;
9061         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9062         board[fromY][BOARD_LEFT] = EmptySquare;
9063     } else if (fromY == 7 && fromX == 3
9064                && board[fromY][fromX] == BlackKing
9065                && toY == 7 && toX == 5) {
9066         board[fromY][fromX] = EmptySquare;
9067         board[toY][toX] = BlackKing;
9068         board[fromY][7] = EmptySquare;
9069         board[toY][4] = BlackRook;
9070     } else if (fromY == 7 && fromX == 3
9071                && board[fromY][fromX] == BlackKing
9072                && toY == 7 && toX == 1) {
9073         board[fromY][fromX] = EmptySquare;
9074         board[toY][toX] = BlackKing;
9075         board[fromY][0] = EmptySquare;
9076         board[toY][2] = BlackRook;
9077     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9078                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9079                && toY < promoRank
9080                ) {
9081         /* black pawn promotion */
9082         board[toY][toX] = CharToPiece(ToLower(promoChar));
9083         if (board[toY][toX] == EmptySquare) {
9084             board[toY][toX] = BlackQueen;
9085         }
9086         if(gameInfo.variant==VariantBughouse ||
9087            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9088             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9089         board[fromY][fromX] = EmptySquare;
9090     } else if ((fromY == 3)
9091                && (toX != fromX)
9092                && gameInfo.variant != VariantXiangqi
9093                && gameInfo.variant != VariantBerolina
9094                && (board[fromY][fromX] == BlackPawn)
9095                && (board[toY][toX] == EmptySquare)) {
9096         board[fromY][fromX] = EmptySquare;
9097         board[toY][toX] = BlackPawn;
9098         captured = board[toY + 1][toX];
9099         board[toY + 1][toX] = EmptySquare;
9100     } else if ((fromY == 3)
9101                && (toX == fromX)
9102                && gameInfo.variant == VariantBerolina
9103                && (board[fromY][fromX] == BlackPawn)
9104                && (board[toY][toX] == EmptySquare)) {
9105         board[fromY][fromX] = EmptySquare;
9106         board[toY][toX] = BlackPawn;
9107         if(oldEP & EP_BEROLIN_A) {
9108                 captured = board[fromY][fromX-1];
9109                 board[fromY][fromX-1] = EmptySquare;
9110         }else{  captured = board[fromY][fromX+1];
9111                 board[fromY][fromX+1] = EmptySquare;
9112         }
9113     } else {
9114         board[toY][toX] = board[fromY][fromX];
9115         board[fromY][fromX] = EmptySquare;
9116     }
9117   }
9118
9119     if (gameInfo.holdingsWidth != 0) {
9120
9121       /* !!A lot more code needs to be written to support holdings  */
9122       /* [HGM] OK, so I have written it. Holdings are stored in the */
9123       /* penultimate board files, so they are automaticlly stored   */
9124       /* in the game history.                                       */
9125       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9126                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9127         /* Delete from holdings, by decreasing count */
9128         /* and erasing image if necessary            */
9129         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9130         if(p < (int) BlackPawn) { /* white drop */
9131              p -= (int)WhitePawn;
9132                  p = PieceToNumber((ChessSquare)p);
9133              if(p >= gameInfo.holdingsSize) p = 0;
9134              if(--board[p][BOARD_WIDTH-2] <= 0)
9135                   board[p][BOARD_WIDTH-1] = EmptySquare;
9136              if((int)board[p][BOARD_WIDTH-2] < 0)
9137                         board[p][BOARD_WIDTH-2] = 0;
9138         } else {                  /* black drop */
9139              p -= (int)BlackPawn;
9140                  p = PieceToNumber((ChessSquare)p);
9141              if(p >= gameInfo.holdingsSize) p = 0;
9142              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9143                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9144              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9145                         board[BOARD_HEIGHT-1-p][1] = 0;
9146         }
9147       }
9148       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9149           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9150         /* [HGM] holdings: Add to holdings, if holdings exist */
9151         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9152                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9153                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9154         }
9155         p = (int) captured;
9156         if (p >= (int) BlackPawn) {
9157           p -= (int)BlackPawn;
9158           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9159                   /* in Shogi restore piece to its original  first */
9160                   captured = (ChessSquare) (DEMOTED captured);
9161                   p = DEMOTED p;
9162           }
9163           p = PieceToNumber((ChessSquare)p);
9164           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9165           board[p][BOARD_WIDTH-2]++;
9166           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9167         } else {
9168           p -= (int)WhitePawn;
9169           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9170                   captured = (ChessSquare) (DEMOTED captured);
9171                   p = DEMOTED p;
9172           }
9173           p = PieceToNumber((ChessSquare)p);
9174           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9175           board[BOARD_HEIGHT-1-p][1]++;
9176           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9177         }
9178       }
9179     } else if (gameInfo.variant == VariantAtomic) {
9180       if (captured != EmptySquare) {
9181         int y, x;
9182         for (y = toY-1; y <= toY+1; y++) {
9183           for (x = toX-1; x <= toX+1; x++) {
9184             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9185                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9186               board[y][x] = EmptySquare;
9187             }
9188           }
9189         }
9190         board[toY][toX] = EmptySquare;
9191       }
9192     }
9193     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9194         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9195     } else
9196     if(promoChar == '+') {
9197         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9198         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9199     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9200         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9201     }
9202     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9203                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9204         // [HGM] superchess: take promotion piece out of holdings
9205         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9206         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9207             if(!--board[k][BOARD_WIDTH-2])
9208                 board[k][BOARD_WIDTH-1] = EmptySquare;
9209         } else {
9210             if(!--board[BOARD_HEIGHT-1-k][1])
9211                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9212         }
9213     }
9214
9215 }
9216
9217 /* Updates forwardMostMove */
9218 void
9219 MakeMove(fromX, fromY, toX, toY, promoChar)
9220      int fromX, fromY, toX, toY;
9221      int promoChar;
9222 {
9223 //    forwardMostMove++; // [HGM] bare: moved downstream
9224
9225     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9226         int timeLeft; static int lastLoadFlag=0; int king, piece;
9227         piece = boards[forwardMostMove][fromY][fromX];
9228         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9229         if(gameInfo.variant == VariantKnightmate)
9230             king += (int) WhiteUnicorn - (int) WhiteKing;
9231         if(forwardMostMove == 0) {
9232             if(blackPlaysFirst)
9233                 fprintf(serverMoves, "%s;", second.tidy);
9234             fprintf(serverMoves, "%s;", first.tidy);
9235             if(!blackPlaysFirst)
9236                 fprintf(serverMoves, "%s;", second.tidy);
9237         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9238         lastLoadFlag = loadFlag;
9239         // print base move
9240         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9241         // print castling suffix
9242         if( toY == fromY && piece == king ) {
9243             if(toX-fromX > 1)
9244                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9245             if(fromX-toX >1)
9246                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9247         }
9248         // e.p. suffix
9249         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9250              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9251              boards[forwardMostMove][toY][toX] == EmptySquare
9252              && fromX != toX && fromY != toY)
9253                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9254         // promotion suffix
9255         if(promoChar != NULLCHAR)
9256                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9257         if(!loadFlag) {
9258             fprintf(serverMoves, "/%d/%d",
9259                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9260             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9261             else                      timeLeft = blackTimeRemaining/1000;
9262             fprintf(serverMoves, "/%d", timeLeft);
9263         }
9264         fflush(serverMoves);
9265     }
9266
9267     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9268       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9269                         0, 1);
9270       return;
9271     }
9272     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9273     if (commentList[forwardMostMove+1] != NULL) {
9274         free(commentList[forwardMostMove+1]);
9275         commentList[forwardMostMove+1] = NULL;
9276     }
9277     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9278     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9279     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9280     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9281     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9282     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9283     gameInfo.result = GameUnfinished;
9284     if (gameInfo.resultDetails != NULL) {
9285         free(gameInfo.resultDetails);
9286         gameInfo.resultDetails = NULL;
9287     }
9288     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9289                               moveList[forwardMostMove - 1]);
9290     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9291                              PosFlags(forwardMostMove - 1),
9292                              fromY, fromX, toY, toX, promoChar,
9293                              parseList[forwardMostMove - 1]);
9294     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9295       case MT_NONE:
9296       case MT_STALEMATE:
9297       default:
9298         break;
9299       case MT_CHECK:
9300         if(gameInfo.variant != VariantShogi)
9301             strcat(parseList[forwardMostMove - 1], "+");
9302         break;
9303       case MT_CHECKMATE:
9304       case MT_STAINMATE:
9305         strcat(parseList[forwardMostMove - 1], "#");
9306         break;
9307     }
9308     if (appData.debugMode) {
9309         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9310     }
9311
9312 }
9313
9314 /* Updates currentMove if not pausing */
9315 void
9316 ShowMove(fromX, fromY, toX, toY)
9317 {
9318     int instant = (gameMode == PlayFromGameFile) ?
9319         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9320     if(appData.noGUI) return;
9321     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9322         if (!instant) {
9323             if (forwardMostMove == currentMove + 1) {
9324                 AnimateMove(boards[forwardMostMove - 1],
9325                             fromX, fromY, toX, toY);
9326             }
9327             if (appData.highlightLastMove) {
9328                 SetHighlights(fromX, fromY, toX, toY);
9329             }
9330         }
9331         currentMove = forwardMostMove;
9332     }
9333
9334     if (instant) return;
9335
9336     DisplayMove(currentMove - 1);
9337     DrawPosition(FALSE, boards[currentMove]);
9338     DisplayBothClocks();
9339     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9340     DisplayBook(currentMove);
9341 }
9342
9343 void SendEgtPath(ChessProgramState *cps)
9344 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9345         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9346
9347         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9348
9349         while(*p) {
9350             char c, *q = name+1, *r, *s;
9351
9352             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9353             while(*p && *p != ',') *q++ = *p++;
9354             *q++ = ':'; *q = 0;
9355             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9356                 strcmp(name, ",nalimov:") == 0 ) {
9357                 // take nalimov path from the menu-changeable option first, if it is defined
9358               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9359                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9360             } else
9361             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9362                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9363                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9364                 s = r = StrStr(s, ":") + 1; // beginning of path info
9365                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9366                 c = *r; *r = 0;             // temporarily null-terminate path info
9367                     *--q = 0;               // strip of trailig ':' from name
9368                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9369                 *r = c;
9370                 SendToProgram(buf,cps);     // send egtbpath command for this format
9371             }
9372             if(*p == ',') p++; // read away comma to position for next format name
9373         }
9374 }
9375
9376 void
9377 InitChessProgram(cps, setup)
9378      ChessProgramState *cps;
9379      int setup; /* [HGM] needed to setup FRC opening position */
9380 {
9381     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9382     if (appData.noChessProgram) return;
9383     hintRequested = FALSE;
9384     bookRequested = FALSE;
9385
9386     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9387     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9388     if(cps->memSize) { /* [HGM] memory */
9389       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9390         SendToProgram(buf, cps);
9391     }
9392     SendEgtPath(cps); /* [HGM] EGT */
9393     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9394       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9395         SendToProgram(buf, cps);
9396     }
9397
9398     SendToProgram(cps->initString, cps);
9399     if (gameInfo.variant != VariantNormal &&
9400         gameInfo.variant != VariantLoadable
9401         /* [HGM] also send variant if board size non-standard */
9402         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9403                                             ) {
9404       char *v = VariantName(gameInfo.variant);
9405       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9406         /* [HGM] in protocol 1 we have to assume all variants valid */
9407         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9408         DisplayFatalError(buf, 0, 1);
9409         return;
9410       }
9411
9412       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9413       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9414       if( gameInfo.variant == VariantXiangqi )
9415            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9416       if( gameInfo.variant == VariantShogi )
9417            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9418       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9419            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9420       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9421           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9422            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9423       if( gameInfo.variant == VariantCourier )
9424            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9425       if( gameInfo.variant == VariantSuper )
9426            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9427       if( gameInfo.variant == VariantGreat )
9428            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9429       if( gameInfo.variant == VariantSChess )
9430            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9431
9432       if(overruled) {
9433         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9434                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9435            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9436            if(StrStr(cps->variants, b) == NULL) {
9437                // specific sized variant not known, check if general sizing allowed
9438                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9439                    if(StrStr(cps->variants, "boardsize") == NULL) {
9440                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9441                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9442                        DisplayFatalError(buf, 0, 1);
9443                        return;
9444                    }
9445                    /* [HGM] here we really should compare with the maximum supported board size */
9446                }
9447            }
9448       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9449       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9450       SendToProgram(buf, cps);
9451     }
9452     currentlyInitializedVariant = gameInfo.variant;
9453
9454     /* [HGM] send opening position in FRC to first engine */
9455     if(setup) {
9456           SendToProgram("force\n", cps);
9457           SendBoard(cps, 0);
9458           /* engine is now in force mode! Set flag to wake it up after first move. */
9459           setboardSpoiledMachineBlack = 1;
9460     }
9461
9462     if (cps->sendICS) {
9463       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9464       SendToProgram(buf, cps);
9465     }
9466     cps->maybeThinking = FALSE;
9467     cps->offeredDraw = 0;
9468     if (!appData.icsActive) {
9469         SendTimeControl(cps, movesPerSession, timeControl,
9470                         timeIncrement, appData.searchDepth,
9471                         searchTime);
9472     }
9473     if (appData.showThinking
9474         // [HGM] thinking: four options require thinking output to be sent
9475         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9476                                 ) {
9477         SendToProgram("post\n", cps);
9478     }
9479     SendToProgram("hard\n", cps);
9480     if (!appData.ponderNextMove) {
9481         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9482            it without being sure what state we are in first.  "hard"
9483            is not a toggle, so that one is OK.
9484          */
9485         SendToProgram("easy\n", cps);
9486     }
9487     if (cps->usePing) {
9488       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9489       SendToProgram(buf, cps);
9490     }
9491     cps->initDone = TRUE;
9492 }
9493
9494
9495 void
9496 StartChessProgram(cps)
9497      ChessProgramState *cps;
9498 {
9499     char buf[MSG_SIZ];
9500     int err;
9501
9502     if (appData.noChessProgram) return;
9503     cps->initDone = FALSE;
9504
9505     if (strcmp(cps->host, "localhost") == 0) {
9506         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9507     } else if (*appData.remoteShell == NULLCHAR) {
9508         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9509     } else {
9510         if (*appData.remoteUser == NULLCHAR) {
9511           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9512                     cps->program);
9513         } else {
9514           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9515                     cps->host, appData.remoteUser, cps->program);
9516         }
9517         err = StartChildProcess(buf, "", &cps->pr);
9518     }
9519
9520     if (err != 0) {
9521       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9522         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9523         if(cps != &first) return;
9524         appData.noChessProgram = TRUE;
9525         ThawUI();
9526         SetNCPMode();
9527 //      DisplayFatalError(buf, err, 1);
9528 //      cps->pr = NoProc;
9529 //      cps->isr = NULL;
9530         return;
9531     }
9532
9533     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9534     if (cps->protocolVersion > 1) {
9535       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9536       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9537       cps->comboCnt = 0;  //                and values of combo boxes
9538       SendToProgram(buf, cps);
9539     } else {
9540       SendToProgram("xboard\n", cps);
9541     }
9542 }
9543
9544 void
9545 TwoMachinesEventIfReady P((void))
9546 {
9547   static int curMess = 0;
9548   if (first.lastPing != first.lastPong) {
9549     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9550     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9551     return;
9552   }
9553   if (second.lastPing != second.lastPong) {
9554     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9555     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9556     return;
9557   }
9558   DisplayMessage("", ""); curMess = 0;
9559   ThawUI();
9560   TwoMachinesEvent();
9561 }
9562
9563 char *
9564 MakeName(char *template)
9565 {
9566     time_t clock;
9567     struct tm *tm;
9568     static char buf[MSG_SIZ];
9569     char *p = buf;
9570     int i;
9571
9572     clock = time((time_t *)NULL);
9573     tm = localtime(&clock);
9574
9575     while(*p++ = *template++) if(p[-1] == '%') {
9576         switch(*template++) {
9577           case 0:   *p = 0; return buf;
9578           case 'Y': i = tm->tm_year+1900; break;
9579           case 'y': i = tm->tm_year-100; break;
9580           case 'M': i = tm->tm_mon+1; break;
9581           case 'd': i = tm->tm_mday; break;
9582           case 'h': i = tm->tm_hour; break;
9583           case 'm': i = tm->tm_min; break;
9584           case 's': i = tm->tm_sec; break;
9585           default:  i = 0;
9586         }
9587         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9588     }
9589     return buf;
9590 }
9591
9592 int
9593 CountPlayers(char *p)
9594 {
9595     int n = 0;
9596     while(p = strchr(p, '\n')) p++, n++; // count participants
9597     return n;
9598 }
9599
9600 FILE *
9601 WriteTourneyFile(char *results)
9602 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9603     FILE *f = fopen(appData.tourneyFile, "w");
9604     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9605         // create a file with tournament description
9606         fprintf(f, "-participants {%s}\n", appData.participants);
9607         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9608         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9609         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9610         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9611         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9612         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9613         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9614         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9615         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9616         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9617         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9618         if(searchTime > 0)
9619                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9620         else {
9621                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9622                 fprintf(f, "-tc %s\n", appData.timeControl);
9623                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9624         }
9625         fprintf(f, "-results \"%s\"\n", results);
9626     }
9627     return f;
9628 }
9629
9630 int
9631 CreateTourney(char *name)
9632 {
9633         FILE *f;
9634         if(name[0] == NULLCHAR) {
9635             if(appData.participants[0])
9636                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9637             return 0;
9638         }
9639         f = fopen(name, "r");
9640         if(f) { // file exists
9641             ASSIGN(appData.tourneyFile, name);
9642             ParseArgsFromFile(f); // parse it
9643         } else {
9644             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9645             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9646                 DisplayError(_("Not enough participants"), 0);
9647                 return 0;
9648             }
9649             ASSIGN(appData.tourneyFile, name);
9650             if((f = WriteTourneyFile("")) == NULL) return 0;
9651         }
9652         fclose(f);
9653         appData.noChessProgram = FALSE;
9654         appData.clockMode = TRUE;
9655         SetGNUMode();
9656         return 1;
9657 }
9658
9659 #define MAXENGINES 1000
9660 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9661
9662 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9663 {
9664     char buf[MSG_SIZ], *p, *q;
9665     int i=1;
9666     while(*names) {
9667         p = names; q = buf;
9668         while(*p && *p != '\n') *q++ = *p++;
9669         *q = 0;
9670         if(engineList[i]) free(engineList[i]);
9671         engineList[i] = strdup(buf);
9672         if(*p == '\n') p++;
9673         TidyProgramName(engineList[i], "localhost", buf);
9674         if(engineMnemonic[i]) free(engineMnemonic[i]);
9675         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9676             strcat(buf, " (");
9677             sscanf(q + 8, "%s", buf + strlen(buf));
9678             strcat(buf, ")");
9679         }
9680         engineMnemonic[i] = strdup(buf);
9681         names = p; i++;
9682       if(i > MAXENGINES - 2) break;
9683     }
9684     engineList[i] = NULL;
9685 }
9686
9687 // following implemented as macro to avoid type limitations
9688 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9689
9690 void SwapEngines(int n)
9691 {   // swap settings for first engine and other engine (so far only some selected options)
9692     int h;
9693     char *p;
9694     if(n == 0) return;
9695     SWAP(directory, p)
9696     SWAP(chessProgram, p)
9697     SWAP(isUCI, h)
9698     SWAP(hasOwnBookUCI, h)
9699     SWAP(protocolVersion, h)
9700     SWAP(reuse, h)
9701     SWAP(scoreIsAbsolute, h)
9702     SWAP(timeOdds, h)
9703     SWAP(logo, p)
9704     SWAP(pgnName, p)
9705 }
9706
9707 void
9708 SetPlayer(int player)
9709 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9710     int i;
9711     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9712     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9713     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9714     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9715     if(mnemonic[i]) {
9716         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9717         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9718         ParseArgsFromString(buf);
9719     }
9720     free(engineName);
9721 }
9722
9723 int
9724 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9725 {   // determine players from game number
9726     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9727
9728     if(appData.tourneyType == 0) {
9729         roundsPerCycle = (nPlayers - 1) | 1;
9730         pairingsPerRound = nPlayers / 2;
9731     } else if(appData.tourneyType > 0) {
9732         roundsPerCycle = nPlayers - appData.tourneyType;
9733         pairingsPerRound = appData.tourneyType;
9734     }
9735     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9736     gamesPerCycle = gamesPerRound * roundsPerCycle;
9737     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9738     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9739     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9740     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9741     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9742     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9743
9744     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9745     if(appData.roundSync) *syncInterval = gamesPerRound;
9746
9747     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9748
9749     if(appData.tourneyType == 0) {
9750         if(curPairing == (nPlayers-1)/2 ) {
9751             *whitePlayer = curRound;
9752             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9753         } else {
9754             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9755             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9756             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9757             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9758         }
9759     } else if(appData.tourneyType > 0) {
9760         *whitePlayer = curPairing;
9761         *blackPlayer = curRound + appData.tourneyType;
9762     }
9763
9764     // take care of white/black alternation per round. 
9765     // For cycles and games this is already taken care of by default, derived from matchGame!
9766     return curRound & 1;
9767 }
9768
9769 int
9770 NextTourneyGame(int nr, int *swapColors)
9771 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9772     char *p, *q;
9773     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9774     FILE *tf;
9775     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9776     tf = fopen(appData.tourneyFile, "r");
9777     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9778     ParseArgsFromFile(tf); fclose(tf);
9779     InitTimeControls(); // TC might be altered from tourney file
9780
9781     nPlayers = CountPlayers(appData.participants); // count participants
9782     if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9783         if(nr>=0 && !pairingReceived) {
9784             char buf[1<<16];
9785             if(pairing.pr == NoProc) StartChessProgram(&pairing);
9786             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9787             SendToProgram(buf, &pairing);
9788             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9789             SendToProgram(buf, &pairing);
9790             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9791         }
9792         pairingReceived = 0;                              // ... so we continue here 
9793         syncInterval = nPlayers/2; *swapColors = 0;
9794         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9795         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9796         matchGame = 1; roundNr = nr / syncInterval + 1;
9797     } else
9798     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9799
9800     if(syncInterval) {
9801         p = q = appData.results;
9802         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9803         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9804             DisplayMessage(_("Waiting for other game(s)"),"");
9805             waitingForGame = TRUE;
9806             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9807             return 0;
9808         }
9809         waitingForGame = FALSE;
9810     }
9811
9812     if(first.pr != NoProc) return 1; // engines already loaded
9813
9814     // redefine engines, engine dir, etc.
9815     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9816     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9817     SwapEngines(1);
9818     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9819     SwapEngines(1);         // and make that valid for second engine by swapping
9820     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9821     InitEngine(&second, 1);
9822     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9823     return 1;
9824 }
9825
9826 void
9827 NextMatchGame()
9828 {   // performs game initialization that does not invoke engines, and then tries to start the game
9829     int firstWhite, swapColors = 0;
9830     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9831     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9832     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9833     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9834     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9835     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9836     Reset(FALSE, first.pr != NoProc);
9837     appData.noChessProgram = FALSE;
9838     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9839     TwoMachinesEvent();
9840 }
9841
9842 void UserAdjudicationEvent( int result )
9843 {
9844     ChessMove gameResult = GameIsDrawn;
9845
9846     if( result > 0 ) {
9847         gameResult = WhiteWins;
9848     }
9849     else if( result < 0 ) {
9850         gameResult = BlackWins;
9851     }
9852
9853     if( gameMode == TwoMachinesPlay ) {
9854         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9855     }
9856 }
9857
9858
9859 // [HGM] save: calculate checksum of game to make games easily identifiable
9860 int StringCheckSum(char *s)
9861 {
9862         int i = 0;
9863         if(s==NULL) return 0;
9864         while(*s) i = i*259 + *s++;
9865         return i;
9866 }
9867
9868 int GameCheckSum()
9869 {
9870         int i, sum=0;
9871         for(i=backwardMostMove; i<forwardMostMove; i++) {
9872                 sum += pvInfoList[i].depth;
9873                 sum += StringCheckSum(parseList[i]);
9874                 sum += StringCheckSum(commentList[i]);
9875                 sum *= 261;
9876         }
9877         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9878         return sum + StringCheckSum(commentList[i]);
9879 } // end of save patch
9880
9881 void
9882 GameEnds(result, resultDetails, whosays)
9883      ChessMove result;
9884      char *resultDetails;
9885      int whosays;
9886 {
9887     GameMode nextGameMode;
9888     int isIcsGame;
9889     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9890
9891     if(endingGame) return; /* [HGM] crash: forbid recursion */
9892     endingGame = 1;
9893     if(twoBoards) { // [HGM] dual: switch back to one board
9894         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9895         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9896     }
9897     if (appData.debugMode) {
9898       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9899               result, resultDetails ? resultDetails : "(null)", whosays);
9900     }
9901
9902     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9903
9904     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9905         /* If we are playing on ICS, the server decides when the
9906            game is over, but the engine can offer to draw, claim
9907            a draw, or resign.
9908          */
9909 #if ZIPPY
9910         if (appData.zippyPlay && first.initDone) {
9911             if (result == GameIsDrawn) {
9912                 /* In case draw still needs to be claimed */
9913                 SendToICS(ics_prefix);
9914                 SendToICS("draw\n");
9915             } else if (StrCaseStr(resultDetails, "resign")) {
9916                 SendToICS(ics_prefix);
9917                 SendToICS("resign\n");
9918             }
9919         }
9920 #endif
9921         endingGame = 0; /* [HGM] crash */
9922         return;
9923     }
9924
9925     /* If we're loading the game from a file, stop */
9926     if (whosays == GE_FILE) {
9927       (void) StopLoadGameTimer();
9928       gameFileFP = NULL;
9929     }
9930
9931     /* Cancel draw offers */
9932     first.offeredDraw = second.offeredDraw = 0;
9933
9934     /* If this is an ICS game, only ICS can really say it's done;
9935        if not, anyone can. */
9936     isIcsGame = (gameMode == IcsPlayingWhite ||
9937                  gameMode == IcsPlayingBlack ||
9938                  gameMode == IcsObserving    ||
9939                  gameMode == IcsExamining);
9940
9941     if (!isIcsGame || whosays == GE_ICS) {
9942         /* OK -- not an ICS game, or ICS said it was done */
9943         StopClocks();
9944         if (!isIcsGame && !appData.noChessProgram)
9945           SetUserThinkingEnables();
9946
9947         /* [HGM] if a machine claims the game end we verify this claim */
9948         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9949             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9950                 char claimer;
9951                 ChessMove trueResult = (ChessMove) -1;
9952
9953                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9954                                             first.twoMachinesColor[0] :
9955                                             second.twoMachinesColor[0] ;
9956
9957                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9958                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9959                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9960                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9961                 } else
9962                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9963                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9964                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9965                 } else
9966                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9967                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9968                 }
9969
9970                 // now verify win claims, but not in drop games, as we don't understand those yet
9971                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9972                                                  || gameInfo.variant == VariantGreat) &&
9973                     (result == WhiteWins && claimer == 'w' ||
9974                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9975                       if (appData.debugMode) {
9976                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9977                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9978                       }
9979                       if(result != trueResult) {
9980                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9981                               result = claimer == 'w' ? BlackWins : WhiteWins;
9982                               resultDetails = buf;
9983                       }
9984                 } else
9985                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9986                     && (forwardMostMove <= backwardMostMove ||
9987                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9988                         (claimer=='b')==(forwardMostMove&1))
9989                                                                                   ) {
9990                       /* [HGM] verify: draws that were not flagged are false claims */
9991                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9992                       result = claimer == 'w' ? BlackWins : WhiteWins;
9993                       resultDetails = buf;
9994                 }
9995                 /* (Claiming a loss is accepted no questions asked!) */
9996             }
9997             /* [HGM] bare: don't allow bare King to win */
9998             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9999                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10000                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10001                && result != GameIsDrawn)
10002             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10003                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10004                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10005                         if(p >= 0 && p <= (int)WhiteKing) k++;
10006                 }
10007                 if (appData.debugMode) {
10008                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10009                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10010                 }
10011                 if(k <= 1) {
10012                         result = GameIsDrawn;
10013                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10014                         resultDetails = buf;
10015                 }
10016             }
10017         }
10018
10019
10020         if(serverMoves != NULL && !loadFlag) { char c = '=';
10021             if(result==WhiteWins) c = '+';
10022             if(result==BlackWins) c = '-';
10023             if(resultDetails != NULL)
10024                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10025         }
10026         if (resultDetails != NULL) {
10027             gameInfo.result = result;
10028             gameInfo.resultDetails = StrSave(resultDetails);
10029
10030             /* display last move only if game was not loaded from file */
10031             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10032                 DisplayMove(currentMove - 1);
10033
10034             if (forwardMostMove != 0) {
10035                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10036                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10037                                                                 ) {
10038                     if (*appData.saveGameFile != NULLCHAR) {
10039                         SaveGameToFile(appData.saveGameFile, TRUE);
10040                     } else if (appData.autoSaveGames) {
10041                         AutoSaveGame();
10042                     }
10043                     if (*appData.savePositionFile != NULLCHAR) {
10044                         SavePositionToFile(appData.savePositionFile);
10045                     }
10046                 }
10047             }
10048
10049             /* Tell program how game ended in case it is learning */
10050             /* [HGM] Moved this to after saving the PGN, just in case */
10051             /* engine died and we got here through time loss. In that */
10052             /* case we will get a fatal error writing the pipe, which */
10053             /* would otherwise lose us the PGN.                       */
10054             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10055             /* output during GameEnds should never be fatal anymore   */
10056             if (gameMode == MachinePlaysWhite ||
10057                 gameMode == MachinePlaysBlack ||
10058                 gameMode == TwoMachinesPlay ||
10059                 gameMode == IcsPlayingWhite ||
10060                 gameMode == IcsPlayingBlack ||
10061                 gameMode == BeginningOfGame) {
10062                 char buf[MSG_SIZ];
10063                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10064                         resultDetails);
10065                 if (first.pr != NoProc) {
10066                     SendToProgram(buf, &first);
10067                 }
10068                 if (second.pr != NoProc &&
10069                     gameMode == TwoMachinesPlay) {
10070                     SendToProgram(buf, &second);
10071                 }
10072             }
10073         }
10074
10075         if (appData.icsActive) {
10076             if (appData.quietPlay &&
10077                 (gameMode == IcsPlayingWhite ||
10078                  gameMode == IcsPlayingBlack)) {
10079                 SendToICS(ics_prefix);
10080                 SendToICS("set shout 1\n");
10081             }
10082             nextGameMode = IcsIdle;
10083             ics_user_moved = FALSE;
10084             /* clean up premove.  It's ugly when the game has ended and the
10085              * premove highlights are still on the board.
10086              */
10087             if (gotPremove) {
10088               gotPremove = FALSE;
10089               ClearPremoveHighlights();
10090               DrawPosition(FALSE, boards[currentMove]);
10091             }
10092             if (whosays == GE_ICS) {
10093                 switch (result) {
10094                 case WhiteWins:
10095                     if (gameMode == IcsPlayingWhite)
10096                         PlayIcsWinSound();
10097                     else if(gameMode == IcsPlayingBlack)
10098                         PlayIcsLossSound();
10099                     break;
10100                 case BlackWins:
10101                     if (gameMode == IcsPlayingBlack)
10102                         PlayIcsWinSound();
10103                     else if(gameMode == IcsPlayingWhite)
10104                         PlayIcsLossSound();
10105                     break;
10106                 case GameIsDrawn:
10107                     PlayIcsDrawSound();
10108                     break;
10109                 default:
10110                     PlayIcsUnfinishedSound();
10111                 }
10112             }
10113         } else if (gameMode == EditGame ||
10114                    gameMode == PlayFromGameFile ||
10115                    gameMode == AnalyzeMode ||
10116                    gameMode == AnalyzeFile) {
10117             nextGameMode = gameMode;
10118         } else {
10119             nextGameMode = EndOfGame;
10120         }
10121         pausing = FALSE;
10122         ModeHighlight();
10123     } else {
10124         nextGameMode = gameMode;
10125     }
10126
10127     if (appData.noChessProgram) {
10128         gameMode = nextGameMode;
10129         ModeHighlight();
10130         endingGame = 0; /* [HGM] crash */
10131         return;
10132     }
10133
10134     if (first.reuse) {
10135         /* Put first chess program into idle state */
10136         if (first.pr != NoProc &&
10137             (gameMode == MachinePlaysWhite ||
10138              gameMode == MachinePlaysBlack ||
10139              gameMode == TwoMachinesPlay ||
10140              gameMode == IcsPlayingWhite ||
10141              gameMode == IcsPlayingBlack ||
10142              gameMode == BeginningOfGame)) {
10143             SendToProgram("force\n", &first);
10144             if (first.usePing) {
10145               char buf[MSG_SIZ];
10146               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10147               SendToProgram(buf, &first);
10148             }
10149         }
10150     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10151         /* Kill off first chess program */
10152         if (first.isr != NULL)
10153           RemoveInputSource(first.isr);
10154         first.isr = NULL;
10155
10156         if (first.pr != NoProc) {
10157             ExitAnalyzeMode();
10158             DoSleep( appData.delayBeforeQuit );
10159             SendToProgram("quit\n", &first);
10160             DoSleep( appData.delayAfterQuit );
10161             DestroyChildProcess(first.pr, first.useSigterm);
10162         }
10163         first.pr = NoProc;
10164     }
10165     if (second.reuse) {
10166         /* Put second chess program into idle state */
10167         if (second.pr != NoProc &&
10168             gameMode == TwoMachinesPlay) {
10169             SendToProgram("force\n", &second);
10170             if (second.usePing) {
10171               char buf[MSG_SIZ];
10172               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10173               SendToProgram(buf, &second);
10174             }
10175         }
10176     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10177         /* Kill off second chess program */
10178         if (second.isr != NULL)
10179           RemoveInputSource(second.isr);
10180         second.isr = NULL;
10181
10182         if (second.pr != NoProc) {
10183             DoSleep( appData.delayBeforeQuit );
10184             SendToProgram("quit\n", &second);
10185             DoSleep( appData.delayAfterQuit );
10186             DestroyChildProcess(second.pr, second.useSigterm);
10187         }
10188         second.pr = NoProc;
10189     }
10190
10191     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10192         char resChar = '=';
10193         switch (result) {
10194         case WhiteWins:
10195           resChar = '+';
10196           if (first.twoMachinesColor[0] == 'w') {
10197             first.matchWins++;
10198           } else {
10199             second.matchWins++;
10200           }
10201           break;
10202         case BlackWins:
10203           resChar = '-';
10204           if (first.twoMachinesColor[0] == 'b') {
10205             first.matchWins++;
10206           } else {
10207             second.matchWins++;
10208           }
10209           break;
10210         case GameUnfinished:
10211           resChar = ' ';
10212         default:
10213           break;
10214         }
10215
10216         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10217         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10218             ReserveGame(nextGame, resChar); // sets nextGame
10219             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10220             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10221         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10222
10223         if (nextGame <= appData.matchGames && !abortMatch) {
10224             gameMode = nextGameMode;
10225             matchGame = nextGame; // this will be overruled in tourney mode!
10226             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10227             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10228             endingGame = 0; /* [HGM] crash */
10229             return;
10230         } else {
10231             gameMode = nextGameMode;
10232             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10233                      first.tidy, second.tidy,
10234                      first.matchWins, second.matchWins,
10235                      appData.matchGames - (first.matchWins + second.matchWins));
10236             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10237             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10238                 first.twoMachinesColor = "black\n";
10239                 second.twoMachinesColor = "white\n";
10240             } else {
10241                 first.twoMachinesColor = "white\n";
10242                 second.twoMachinesColor = "black\n";
10243             }
10244         }
10245     }
10246     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10247         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10248       ExitAnalyzeMode();
10249     gameMode = nextGameMode;
10250     ModeHighlight();
10251     endingGame = 0;  /* [HGM] crash */
10252     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10253         if(matchMode == TRUE) { // match through command line: exit with or without popup
10254             if(ranking) {
10255                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10256                 else ExitEvent(0);
10257             } else DisplayFatalError(buf, 0, 0);
10258         } else { // match through menu; just stop, with or without popup
10259             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10260             if(ranking){
10261                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10262             } else DisplayNote(buf);
10263       }
10264       if(ranking) free(ranking);
10265     }
10266 }
10267
10268 /* Assumes program was just initialized (initString sent).
10269    Leaves program in force mode. */
10270 void
10271 FeedMovesToProgram(cps, upto)
10272      ChessProgramState *cps;
10273      int upto;
10274 {
10275     int i;
10276
10277     if (appData.debugMode)
10278       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10279               startedFromSetupPosition ? "position and " : "",
10280               backwardMostMove, upto, cps->which);
10281     if(currentlyInitializedVariant != gameInfo.variant) {
10282       char buf[MSG_SIZ];
10283         // [HGM] variantswitch: make engine aware of new variant
10284         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10285                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10286         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10287         SendToProgram(buf, cps);
10288         currentlyInitializedVariant = gameInfo.variant;
10289     }
10290     SendToProgram("force\n", cps);
10291     if (startedFromSetupPosition) {
10292         SendBoard(cps, backwardMostMove);
10293     if (appData.debugMode) {
10294         fprintf(debugFP, "feedMoves\n");
10295     }
10296     }
10297     for (i = backwardMostMove; i < upto; i++) {
10298         SendMoveToProgram(i, cps);
10299     }
10300 }
10301
10302
10303 int
10304 ResurrectChessProgram()
10305 {
10306      /* The chess program may have exited.
10307         If so, restart it and feed it all the moves made so far. */
10308     static int doInit = 0;
10309
10310     if (appData.noChessProgram) return 1;
10311
10312     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10313         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10314         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10315         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10316     } else {
10317         if (first.pr != NoProc) return 1;
10318         StartChessProgram(&first);
10319     }
10320     InitChessProgram(&first, FALSE);
10321     FeedMovesToProgram(&first, currentMove);
10322
10323     if (!first.sendTime) {
10324         /* can't tell gnuchess what its clock should read,
10325            so we bow to its notion. */
10326         ResetClocks();
10327         timeRemaining[0][currentMove] = whiteTimeRemaining;
10328         timeRemaining[1][currentMove] = blackTimeRemaining;
10329     }
10330
10331     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10332                 appData.icsEngineAnalyze) && first.analysisSupport) {
10333       SendToProgram("analyze\n", &first);
10334       first.analyzing = TRUE;
10335     }
10336     return 1;
10337 }
10338
10339 /*
10340  * Button procedures
10341  */
10342 void
10343 Reset(redraw, init)
10344      int redraw, init;
10345 {
10346     int i;
10347
10348     if (appData.debugMode) {
10349         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10350                 redraw, init, gameMode);
10351     }
10352     CleanupTail(); // [HGM] vari: delete any stored variations
10353     pausing = pauseExamInvalid = FALSE;
10354     startedFromSetupPosition = blackPlaysFirst = FALSE;
10355     firstMove = TRUE;
10356     whiteFlag = blackFlag = FALSE;
10357     userOfferedDraw = FALSE;
10358     hintRequested = bookRequested = FALSE;
10359     first.maybeThinking = FALSE;
10360     second.maybeThinking = FALSE;
10361     first.bookSuspend = FALSE; // [HGM] book
10362     second.bookSuspend = FALSE;
10363     thinkOutput[0] = NULLCHAR;
10364     lastHint[0] = NULLCHAR;
10365     ClearGameInfo(&gameInfo);
10366     gameInfo.variant = StringToVariant(appData.variant);
10367     ics_user_moved = ics_clock_paused = FALSE;
10368     ics_getting_history = H_FALSE;
10369     ics_gamenum = -1;
10370     white_holding[0] = black_holding[0] = NULLCHAR;
10371     ClearProgramStats();
10372     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10373
10374     ResetFrontEnd();
10375     ClearHighlights();
10376     flipView = appData.flipView;
10377     ClearPremoveHighlights();
10378     gotPremove = FALSE;
10379     alarmSounded = FALSE;
10380
10381     GameEnds(EndOfFile, NULL, GE_PLAYER);
10382     if(appData.serverMovesName != NULL) {
10383         /* [HGM] prepare to make moves file for broadcasting */
10384         clock_t t = clock();
10385         if(serverMoves != NULL) fclose(serverMoves);
10386         serverMoves = fopen(appData.serverMovesName, "r");
10387         if(serverMoves != NULL) {
10388             fclose(serverMoves);
10389             /* delay 15 sec before overwriting, so all clients can see end */
10390             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10391         }
10392         serverMoves = fopen(appData.serverMovesName, "w");
10393     }
10394
10395     ExitAnalyzeMode();
10396     gameMode = BeginningOfGame;
10397     ModeHighlight();
10398     if(appData.icsActive) gameInfo.variant = VariantNormal;
10399     currentMove = forwardMostMove = backwardMostMove = 0;
10400     InitPosition(redraw);
10401     for (i = 0; i < MAX_MOVES; i++) {
10402         if (commentList[i] != NULL) {
10403             free(commentList[i]);
10404             commentList[i] = NULL;
10405         }
10406     }
10407     ResetClocks();
10408     timeRemaining[0][0] = whiteTimeRemaining;
10409     timeRemaining[1][0] = blackTimeRemaining;
10410
10411     if (first.pr == NULL) {
10412         StartChessProgram(&first);
10413     }
10414     if (init) {
10415             InitChessProgram(&first, startedFromSetupPosition);
10416     }
10417     DisplayTitle("");
10418     DisplayMessage("", "");
10419     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10420     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10421 }
10422
10423 void
10424 AutoPlayGameLoop()
10425 {
10426     for (;;) {
10427         if (!AutoPlayOneMove())
10428           return;
10429         if (matchMode || appData.timeDelay == 0)
10430           continue;
10431         if (appData.timeDelay < 0)
10432           return;
10433         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10434         break;
10435     }
10436 }
10437
10438
10439 int
10440 AutoPlayOneMove()
10441 {
10442     int fromX, fromY, toX, toY;
10443
10444     if (appData.debugMode) {
10445       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10446     }
10447
10448     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10449       return FALSE;
10450
10451     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10452       pvInfoList[currentMove].depth = programStats.depth;
10453       pvInfoList[currentMove].score = programStats.score;
10454       pvInfoList[currentMove].time  = 0;
10455       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10456     }
10457
10458     if (currentMove >= forwardMostMove) {
10459       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10460       gameMode = EditGame;
10461       ModeHighlight();
10462
10463       /* [AS] Clear current move marker at the end of a game */
10464       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10465
10466       return FALSE;
10467     }
10468
10469     toX = moveList[currentMove][2] - AAA;
10470     toY = moveList[currentMove][3] - ONE;
10471
10472     if (moveList[currentMove][1] == '@') {
10473         if (appData.highlightLastMove) {
10474             SetHighlights(-1, -1, toX, toY);
10475         }
10476     } else {
10477         fromX = moveList[currentMove][0] - AAA;
10478         fromY = moveList[currentMove][1] - ONE;
10479
10480         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10481
10482         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10483
10484         if (appData.highlightLastMove) {
10485             SetHighlights(fromX, fromY, toX, toY);
10486         }
10487     }
10488     DisplayMove(currentMove);
10489     SendMoveToProgram(currentMove++, &first);
10490     DisplayBothClocks();
10491     DrawPosition(FALSE, boards[currentMove]);
10492     // [HGM] PV info: always display, routine tests if empty
10493     DisplayComment(currentMove - 1, commentList[currentMove]);
10494     return TRUE;
10495 }
10496
10497
10498 int
10499 LoadGameOneMove(readAhead)
10500      ChessMove readAhead;
10501 {
10502     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10503     char promoChar = NULLCHAR;
10504     ChessMove moveType;
10505     char move[MSG_SIZ];
10506     char *p, *q;
10507
10508     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10509         gameMode != AnalyzeMode && gameMode != Training) {
10510         gameFileFP = NULL;
10511         return FALSE;
10512     }
10513
10514     yyboardindex = forwardMostMove;
10515     if (readAhead != EndOfFile) {
10516       moveType = readAhead;
10517     } else {
10518       if (gameFileFP == NULL)
10519           return FALSE;
10520       moveType = (ChessMove) Myylex();
10521     }
10522
10523     done = FALSE;
10524     switch (moveType) {
10525       case Comment:
10526         if (appData.debugMode)
10527           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10528         p = yy_text;
10529
10530         /* append the comment but don't display it */
10531         AppendComment(currentMove, p, FALSE);
10532         return TRUE;
10533
10534       case WhiteCapturesEnPassant:
10535       case BlackCapturesEnPassant:
10536       case WhitePromotion:
10537       case BlackPromotion:
10538       case WhiteNonPromotion:
10539       case BlackNonPromotion:
10540       case NormalMove:
10541       case WhiteKingSideCastle:
10542       case WhiteQueenSideCastle:
10543       case BlackKingSideCastle:
10544       case BlackQueenSideCastle:
10545       case WhiteKingSideCastleWild:
10546       case WhiteQueenSideCastleWild:
10547       case BlackKingSideCastleWild:
10548       case BlackQueenSideCastleWild:
10549       /* PUSH Fabien */
10550       case WhiteHSideCastleFR:
10551       case WhiteASideCastleFR:
10552       case BlackHSideCastleFR:
10553       case BlackASideCastleFR:
10554       /* POP Fabien */
10555         if (appData.debugMode)
10556           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10557         fromX = currentMoveString[0] - AAA;
10558         fromY = currentMoveString[1] - ONE;
10559         toX = currentMoveString[2] - AAA;
10560         toY = currentMoveString[3] - ONE;
10561         promoChar = currentMoveString[4];
10562         break;
10563
10564       case WhiteDrop:
10565       case BlackDrop:
10566         if (appData.debugMode)
10567           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10568         fromX = moveType == WhiteDrop ?
10569           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10570         (int) CharToPiece(ToLower(currentMoveString[0]));
10571         fromY = DROP_RANK;
10572         toX = currentMoveString[2] - AAA;
10573         toY = currentMoveString[3] - ONE;
10574         break;
10575
10576       case WhiteWins:
10577       case BlackWins:
10578       case GameIsDrawn:
10579       case GameUnfinished:
10580         if (appData.debugMode)
10581           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10582         p = strchr(yy_text, '{');
10583         if (p == NULL) p = strchr(yy_text, '(');
10584         if (p == NULL) {
10585             p = yy_text;
10586             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10587         } else {
10588             q = strchr(p, *p == '{' ? '}' : ')');
10589             if (q != NULL) *q = NULLCHAR;
10590             p++;
10591         }
10592         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10593         GameEnds(moveType, p, GE_FILE);
10594         done = TRUE;
10595         if (cmailMsgLoaded) {
10596             ClearHighlights();
10597             flipView = WhiteOnMove(currentMove);
10598             if (moveType == GameUnfinished) flipView = !flipView;
10599             if (appData.debugMode)
10600               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10601         }
10602         break;
10603
10604       case EndOfFile:
10605         if (appData.debugMode)
10606           fprintf(debugFP, "Parser hit end of file\n");
10607         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10608           case MT_NONE:
10609           case MT_CHECK:
10610             break;
10611           case MT_CHECKMATE:
10612           case MT_STAINMATE:
10613             if (WhiteOnMove(currentMove)) {
10614                 GameEnds(BlackWins, "Black mates", GE_FILE);
10615             } else {
10616                 GameEnds(WhiteWins, "White mates", GE_FILE);
10617             }
10618             break;
10619           case MT_STALEMATE:
10620             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10621             break;
10622         }
10623         done = TRUE;
10624         break;
10625
10626       case MoveNumberOne:
10627         if (lastLoadGameStart == GNUChessGame) {
10628             /* GNUChessGames have numbers, but they aren't move numbers */
10629             if (appData.debugMode)
10630               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10631                       yy_text, (int) moveType);
10632             return LoadGameOneMove(EndOfFile); /* tail recursion */
10633         }
10634         /* else fall thru */
10635
10636       case XBoardGame:
10637       case GNUChessGame:
10638       case PGNTag:
10639         /* Reached start of next game in file */
10640         if (appData.debugMode)
10641           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10642         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10643           case MT_NONE:
10644           case MT_CHECK:
10645             break;
10646           case MT_CHECKMATE:
10647           case MT_STAINMATE:
10648             if (WhiteOnMove(currentMove)) {
10649                 GameEnds(BlackWins, "Black mates", GE_FILE);
10650             } else {
10651                 GameEnds(WhiteWins, "White mates", GE_FILE);
10652             }
10653             break;
10654           case MT_STALEMATE:
10655             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10656             break;
10657         }
10658         done = TRUE;
10659         break;
10660
10661       case PositionDiagram:     /* should not happen; ignore */
10662       case ElapsedTime:         /* ignore */
10663       case NAG:                 /* ignore */
10664         if (appData.debugMode)
10665           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10666                   yy_text, (int) moveType);
10667         return LoadGameOneMove(EndOfFile); /* tail recursion */
10668
10669       case IllegalMove:
10670         if (appData.testLegality) {
10671             if (appData.debugMode)
10672               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10673             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10674                     (forwardMostMove / 2) + 1,
10675                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10676             DisplayError(move, 0);
10677             done = TRUE;
10678         } else {
10679             if (appData.debugMode)
10680               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10681                       yy_text, currentMoveString);
10682             fromX = currentMoveString[0] - AAA;
10683             fromY = currentMoveString[1] - ONE;
10684             toX = currentMoveString[2] - AAA;
10685             toY = currentMoveString[3] - ONE;
10686             promoChar = currentMoveString[4];
10687         }
10688         break;
10689
10690       case AmbiguousMove:
10691         if (appData.debugMode)
10692           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10693         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10694                 (forwardMostMove / 2) + 1,
10695                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10696         DisplayError(move, 0);
10697         done = TRUE;
10698         break;
10699
10700       default:
10701       case ImpossibleMove:
10702         if (appData.debugMode)
10703           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10704         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10705                 (forwardMostMove / 2) + 1,
10706                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10707         DisplayError(move, 0);
10708         done = TRUE;
10709         break;
10710     }
10711
10712     if (done) {
10713         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10714             DrawPosition(FALSE, boards[currentMove]);
10715             DisplayBothClocks();
10716             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10717               DisplayComment(currentMove - 1, commentList[currentMove]);
10718         }
10719         (void) StopLoadGameTimer();
10720         gameFileFP = NULL;
10721         cmailOldMove = forwardMostMove;
10722         return FALSE;
10723     } else {
10724         /* currentMoveString is set as a side-effect of yylex */
10725
10726         thinkOutput[0] = NULLCHAR;
10727         MakeMove(fromX, fromY, toX, toY, promoChar);
10728         currentMove = forwardMostMove;
10729         return TRUE;
10730     }
10731 }
10732
10733 /* Load the nth game from the given file */
10734 int
10735 LoadGameFromFile(filename, n, title, useList)
10736      char *filename;
10737      int n;
10738      char *title;
10739      /*Boolean*/ int useList;
10740 {
10741     FILE *f;
10742     char buf[MSG_SIZ];
10743
10744     if (strcmp(filename, "-") == 0) {
10745         f = stdin;
10746         title = "stdin";
10747     } else {
10748         f = fopen(filename, "rb");
10749         if (f == NULL) {
10750           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10751             DisplayError(buf, errno);
10752             return FALSE;
10753         }
10754     }
10755     if (fseek(f, 0, 0) == -1) {
10756         /* f is not seekable; probably a pipe */
10757         useList = FALSE;
10758     }
10759     if (useList && n == 0) {
10760         int error = GameListBuild(f);
10761         if (error) {
10762             DisplayError(_("Cannot build game list"), error);
10763         } else if (!ListEmpty(&gameList) &&
10764                    ((ListGame *) gameList.tailPred)->number > 1) {
10765             GameListPopUp(f, title);
10766             return TRUE;
10767         }
10768         GameListDestroy();
10769         n = 1;
10770     }
10771     if (n == 0) n = 1;
10772     return LoadGame(f, n, title, FALSE);
10773 }
10774
10775
10776 void
10777 MakeRegisteredMove()
10778 {
10779     int fromX, fromY, toX, toY;
10780     char promoChar;
10781     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10782         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10783           case CMAIL_MOVE:
10784           case CMAIL_DRAW:
10785             if (appData.debugMode)
10786               fprintf(debugFP, "Restoring %s for game %d\n",
10787                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10788
10789             thinkOutput[0] = NULLCHAR;
10790             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10791             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10792             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10793             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10794             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10795             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10796             MakeMove(fromX, fromY, toX, toY, promoChar);
10797             ShowMove(fromX, fromY, toX, toY);
10798
10799             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10800               case MT_NONE:
10801               case MT_CHECK:
10802                 break;
10803
10804               case MT_CHECKMATE:
10805               case MT_STAINMATE:
10806                 if (WhiteOnMove(currentMove)) {
10807                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10808                 } else {
10809                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10810                 }
10811                 break;
10812
10813               case MT_STALEMATE:
10814                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10815                 break;
10816             }
10817
10818             break;
10819
10820           case CMAIL_RESIGN:
10821             if (WhiteOnMove(currentMove)) {
10822                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10823             } else {
10824                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10825             }
10826             break;
10827
10828           case CMAIL_ACCEPT:
10829             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10830             break;
10831
10832           default:
10833             break;
10834         }
10835     }
10836
10837     return;
10838 }
10839
10840 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10841 int
10842 CmailLoadGame(f, gameNumber, title, useList)
10843      FILE *f;
10844      int gameNumber;
10845      char *title;
10846      int useList;
10847 {
10848     int retVal;
10849
10850     if (gameNumber > nCmailGames) {
10851         DisplayError(_("No more games in this message"), 0);
10852         return FALSE;
10853     }
10854     if (f == lastLoadGameFP) {
10855         int offset = gameNumber - lastLoadGameNumber;
10856         if (offset == 0) {
10857             cmailMsg[0] = NULLCHAR;
10858             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10859                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10860                 nCmailMovesRegistered--;
10861             }
10862             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10863             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10864                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10865             }
10866         } else {
10867             if (! RegisterMove()) return FALSE;
10868         }
10869     }
10870
10871     retVal = LoadGame(f, gameNumber, title, useList);
10872
10873     /* Make move registered during previous look at this game, if any */
10874     MakeRegisteredMove();
10875
10876     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10877         commentList[currentMove]
10878           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10879         DisplayComment(currentMove - 1, commentList[currentMove]);
10880     }
10881
10882     return retVal;
10883 }
10884
10885 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10886 int
10887 ReloadGame(offset)
10888      int offset;
10889 {
10890     int gameNumber = lastLoadGameNumber + offset;
10891     if (lastLoadGameFP == NULL) {
10892         DisplayError(_("No game has been loaded yet"), 0);
10893         return FALSE;
10894     }
10895     if (gameNumber <= 0) {
10896         DisplayError(_("Can't back up any further"), 0);
10897         return FALSE;
10898     }
10899     if (cmailMsgLoaded) {
10900         return CmailLoadGame(lastLoadGameFP, gameNumber,
10901                              lastLoadGameTitle, lastLoadGameUseList);
10902     } else {
10903         return LoadGame(lastLoadGameFP, gameNumber,
10904                         lastLoadGameTitle, lastLoadGameUseList);
10905     }
10906 }
10907
10908
10909
10910 /* Load the nth game from open file f */
10911 int
10912 LoadGame(f, gameNumber, title, useList)
10913      FILE *f;
10914      int gameNumber;
10915      char *title;
10916      int useList;
10917 {
10918     ChessMove cm;
10919     char buf[MSG_SIZ];
10920     int gn = gameNumber;
10921     ListGame *lg = NULL;
10922     int numPGNTags = 0;
10923     int err;
10924     GameMode oldGameMode;
10925     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10926
10927     if (appData.debugMode)
10928         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10929
10930     if (gameMode == Training )
10931         SetTrainingModeOff();
10932
10933     oldGameMode = gameMode;
10934     if (gameMode != BeginningOfGame) {
10935       Reset(FALSE, TRUE);
10936     }
10937
10938     gameFileFP = f;
10939     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10940         fclose(lastLoadGameFP);
10941     }
10942
10943     if (useList) {
10944         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10945
10946         if (lg) {
10947             fseek(f, lg->offset, 0);
10948             GameListHighlight(gameNumber);
10949             gn = 1;
10950         }
10951         else {
10952             DisplayError(_("Game number out of range"), 0);
10953             return FALSE;
10954         }
10955     } else {
10956         GameListDestroy();
10957         if (fseek(f, 0, 0) == -1) {
10958             if (f == lastLoadGameFP ?
10959                 gameNumber == lastLoadGameNumber + 1 :
10960                 gameNumber == 1) {
10961                 gn = 1;
10962             } else {
10963                 DisplayError(_("Can't seek on game file"), 0);
10964                 return FALSE;
10965             }
10966         }
10967     }
10968     lastLoadGameFP = f;
10969     lastLoadGameNumber = gameNumber;
10970     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10971     lastLoadGameUseList = useList;
10972
10973     yynewfile(f);
10974
10975     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10976       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10977                 lg->gameInfo.black);
10978             DisplayTitle(buf);
10979     } else if (*title != NULLCHAR) {
10980         if (gameNumber > 1) {
10981           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10982             DisplayTitle(buf);
10983         } else {
10984             DisplayTitle(title);
10985         }
10986     }
10987
10988     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10989         gameMode = PlayFromGameFile;
10990         ModeHighlight();
10991     }
10992
10993     currentMove = forwardMostMove = backwardMostMove = 0;
10994     CopyBoard(boards[0], initialPosition);
10995     StopClocks();
10996
10997     /*
10998      * Skip the first gn-1 games in the file.
10999      * Also skip over anything that precedes an identifiable
11000      * start of game marker, to avoid being confused by
11001      * garbage at the start of the file.  Currently
11002      * recognized start of game markers are the move number "1",
11003      * the pattern "gnuchess .* game", the pattern
11004      * "^[#;%] [^ ]* game file", and a PGN tag block.
11005      * A game that starts with one of the latter two patterns
11006      * will also have a move number 1, possibly
11007      * following a position diagram.
11008      * 5-4-02: Let's try being more lenient and allowing a game to
11009      * start with an unnumbered move.  Does that break anything?
11010      */
11011     cm = lastLoadGameStart = EndOfFile;
11012     while (gn > 0) {
11013         yyboardindex = forwardMostMove;
11014         cm = (ChessMove) Myylex();
11015         switch (cm) {
11016           case EndOfFile:
11017             if (cmailMsgLoaded) {
11018                 nCmailGames = CMAIL_MAX_GAMES - gn;
11019             } else {
11020                 Reset(TRUE, TRUE);
11021                 DisplayError(_("Game not found in file"), 0);
11022             }
11023             return FALSE;
11024
11025           case GNUChessGame:
11026           case XBoardGame:
11027             gn--;
11028             lastLoadGameStart = cm;
11029             break;
11030
11031           case MoveNumberOne:
11032             switch (lastLoadGameStart) {
11033               case GNUChessGame:
11034               case XBoardGame:
11035               case PGNTag:
11036                 break;
11037               case MoveNumberOne:
11038               case EndOfFile:
11039                 gn--;           /* count this game */
11040                 lastLoadGameStart = cm;
11041                 break;
11042               default:
11043                 /* impossible */
11044                 break;
11045             }
11046             break;
11047
11048           case PGNTag:
11049             switch (lastLoadGameStart) {
11050               case GNUChessGame:
11051               case PGNTag:
11052               case MoveNumberOne:
11053               case EndOfFile:
11054                 gn--;           /* count this game */
11055                 lastLoadGameStart = cm;
11056                 break;
11057               case XBoardGame:
11058                 lastLoadGameStart = cm; /* game counted already */
11059                 break;
11060               default:
11061                 /* impossible */
11062                 break;
11063             }
11064             if (gn > 0) {
11065                 do {
11066                     yyboardindex = forwardMostMove;
11067                     cm = (ChessMove) Myylex();
11068                 } while (cm == PGNTag || cm == Comment);
11069             }
11070             break;
11071
11072           case WhiteWins:
11073           case BlackWins:
11074           case GameIsDrawn:
11075             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11076                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11077                     != CMAIL_OLD_RESULT) {
11078                     nCmailResults ++ ;
11079                     cmailResult[  CMAIL_MAX_GAMES
11080                                 - gn - 1] = CMAIL_OLD_RESULT;
11081                 }
11082             }
11083             break;
11084
11085           case NormalMove:
11086             /* Only a NormalMove can be at the start of a game
11087              * without a position diagram. */
11088             if (lastLoadGameStart == EndOfFile ) {
11089               gn--;
11090               lastLoadGameStart = MoveNumberOne;
11091             }
11092             break;
11093
11094           default:
11095             break;
11096         }
11097     }
11098
11099     if (appData.debugMode)
11100       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11101
11102     if (cm == XBoardGame) {
11103         /* Skip any header junk before position diagram and/or move 1 */
11104         for (;;) {
11105             yyboardindex = forwardMostMove;
11106             cm = (ChessMove) Myylex();
11107
11108             if (cm == EndOfFile ||
11109                 cm == GNUChessGame || cm == XBoardGame) {
11110                 /* Empty game; pretend end-of-file and handle later */
11111                 cm = EndOfFile;
11112                 break;
11113             }
11114
11115             if (cm == MoveNumberOne || cm == PositionDiagram ||
11116                 cm == PGNTag || cm == Comment)
11117               break;
11118         }
11119     } else if (cm == GNUChessGame) {
11120         if (gameInfo.event != NULL) {
11121             free(gameInfo.event);
11122         }
11123         gameInfo.event = StrSave(yy_text);
11124     }
11125
11126     startedFromSetupPosition = FALSE;
11127     while (cm == PGNTag) {
11128         if (appData.debugMode)
11129           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11130         err = ParsePGNTag(yy_text, &gameInfo);
11131         if (!err) numPGNTags++;
11132
11133         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11134         if(gameInfo.variant != oldVariant) {
11135             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11136             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11137             InitPosition(TRUE);
11138             oldVariant = gameInfo.variant;
11139             if (appData.debugMode)
11140               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11141         }
11142
11143
11144         if (gameInfo.fen != NULL) {
11145           Board initial_position;
11146           startedFromSetupPosition = TRUE;
11147           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11148             Reset(TRUE, TRUE);
11149             DisplayError(_("Bad FEN position in file"), 0);
11150             return FALSE;
11151           }
11152           CopyBoard(boards[0], initial_position);
11153           if (blackPlaysFirst) {
11154             currentMove = forwardMostMove = backwardMostMove = 1;
11155             CopyBoard(boards[1], initial_position);
11156             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11157             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11158             timeRemaining[0][1] = whiteTimeRemaining;
11159             timeRemaining[1][1] = blackTimeRemaining;
11160             if (commentList[0] != NULL) {
11161               commentList[1] = commentList[0];
11162               commentList[0] = NULL;
11163             }
11164           } else {
11165             currentMove = forwardMostMove = backwardMostMove = 0;
11166           }
11167           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11168           {   int i;
11169               initialRulePlies = FENrulePlies;
11170               for( i=0; i< nrCastlingRights; i++ )
11171                   initialRights[i] = initial_position[CASTLING][i];
11172           }
11173           yyboardindex = forwardMostMove;
11174           free(gameInfo.fen);
11175           gameInfo.fen = NULL;
11176         }
11177
11178         yyboardindex = forwardMostMove;
11179         cm = (ChessMove) Myylex();
11180
11181         /* Handle comments interspersed among the tags */
11182         while (cm == Comment) {
11183             char *p;
11184             if (appData.debugMode)
11185               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11186             p = yy_text;
11187             AppendComment(currentMove, p, FALSE);
11188             yyboardindex = forwardMostMove;
11189             cm = (ChessMove) Myylex();
11190         }
11191     }
11192
11193     /* don't rely on existence of Event tag since if game was
11194      * pasted from clipboard the Event tag may not exist
11195      */
11196     if (numPGNTags > 0){
11197         char *tags;
11198         if (gameInfo.variant == VariantNormal) {
11199           VariantClass v = StringToVariant(gameInfo.event);
11200           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11201           if(v < VariantShogi) gameInfo.variant = v;
11202         }
11203         if (!matchMode) {
11204           if( appData.autoDisplayTags ) {
11205             tags = PGNTags(&gameInfo);
11206             TagsPopUp(tags, CmailMsg());
11207             free(tags);
11208           }
11209         }
11210     } else {
11211         /* Make something up, but don't display it now */
11212         SetGameInfo();
11213         TagsPopDown();
11214     }
11215
11216     if (cm == PositionDiagram) {
11217         int i, j;
11218         char *p;
11219         Board initial_position;
11220
11221         if (appData.debugMode)
11222           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11223
11224         if (!startedFromSetupPosition) {
11225             p = yy_text;
11226             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11227               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11228                 switch (*p) {
11229                   case '{':
11230                   case '[':
11231                   case '-':
11232                   case ' ':
11233                   case '\t':
11234                   case '\n':
11235                   case '\r':
11236                     break;
11237                   default:
11238                     initial_position[i][j++] = CharToPiece(*p);
11239                     break;
11240                 }
11241             while (*p == ' ' || *p == '\t' ||
11242                    *p == '\n' || *p == '\r') p++;
11243
11244             if (strncmp(p, "black", strlen("black"))==0)
11245               blackPlaysFirst = TRUE;
11246             else
11247               blackPlaysFirst = FALSE;
11248             startedFromSetupPosition = TRUE;
11249
11250             CopyBoard(boards[0], initial_position);
11251             if (blackPlaysFirst) {
11252                 currentMove = forwardMostMove = backwardMostMove = 1;
11253                 CopyBoard(boards[1], initial_position);
11254                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11255                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11256                 timeRemaining[0][1] = whiteTimeRemaining;
11257                 timeRemaining[1][1] = blackTimeRemaining;
11258                 if (commentList[0] != NULL) {
11259                     commentList[1] = commentList[0];
11260                     commentList[0] = NULL;
11261                 }
11262             } else {
11263                 currentMove = forwardMostMove = backwardMostMove = 0;
11264             }
11265         }
11266         yyboardindex = forwardMostMove;
11267         cm = (ChessMove) Myylex();
11268     }
11269
11270     if (first.pr == NoProc) {
11271         StartChessProgram(&first);
11272     }
11273     InitChessProgram(&first, FALSE);
11274     SendToProgram("force\n", &first);
11275     if (startedFromSetupPosition) {
11276         SendBoard(&first, forwardMostMove);
11277     if (appData.debugMode) {
11278         fprintf(debugFP, "Load Game\n");
11279     }
11280         DisplayBothClocks();
11281     }
11282
11283     /* [HGM] server: flag to write setup moves in broadcast file as one */
11284     loadFlag = appData.suppressLoadMoves;
11285
11286     while (cm == Comment) {
11287         char *p;
11288         if (appData.debugMode)
11289           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11290         p = yy_text;
11291         AppendComment(currentMove, p, FALSE);
11292         yyboardindex = forwardMostMove;
11293         cm = (ChessMove) Myylex();
11294     }
11295
11296     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11297         cm == WhiteWins || cm == BlackWins ||
11298         cm == GameIsDrawn || cm == GameUnfinished) {
11299         DisplayMessage("", _("No moves in game"));
11300         if (cmailMsgLoaded) {
11301             if (appData.debugMode)
11302               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11303             ClearHighlights();
11304             flipView = FALSE;
11305         }
11306         DrawPosition(FALSE, boards[currentMove]);
11307         DisplayBothClocks();
11308         gameMode = EditGame;
11309         ModeHighlight();
11310         gameFileFP = NULL;
11311         cmailOldMove = 0;
11312         return TRUE;
11313     }
11314
11315     // [HGM] PV info: routine tests if comment empty
11316     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11317         DisplayComment(currentMove - 1, commentList[currentMove]);
11318     }
11319     if (!matchMode && appData.timeDelay != 0)
11320       DrawPosition(FALSE, boards[currentMove]);
11321
11322     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11323       programStats.ok_to_send = 1;
11324     }
11325
11326     /* if the first token after the PGN tags is a move
11327      * and not move number 1, retrieve it from the parser
11328      */
11329     if (cm != MoveNumberOne)
11330         LoadGameOneMove(cm);
11331
11332     /* load the remaining moves from the file */
11333     while (LoadGameOneMove(EndOfFile)) {
11334       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11335       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11336     }
11337
11338     /* rewind to the start of the game */
11339     currentMove = backwardMostMove;
11340
11341     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11342
11343     if (oldGameMode == AnalyzeFile ||
11344         oldGameMode == AnalyzeMode) {
11345       AnalyzeFileEvent();
11346     }
11347
11348     if (matchMode || appData.timeDelay == 0) {
11349       ToEndEvent();
11350       gameMode = EditGame;
11351       ModeHighlight();
11352     } else if (appData.timeDelay > 0) {
11353       AutoPlayGameLoop();
11354     }
11355
11356     if (appData.debugMode)
11357         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11358
11359     loadFlag = 0; /* [HGM] true game starts */
11360     return TRUE;
11361 }
11362
11363 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11364 int
11365 ReloadPosition(offset)
11366      int offset;
11367 {
11368     int positionNumber = lastLoadPositionNumber + offset;
11369     if (lastLoadPositionFP == NULL) {
11370         DisplayError(_("No position has been loaded yet"), 0);
11371         return FALSE;
11372     }
11373     if (positionNumber <= 0) {
11374         DisplayError(_("Can't back up any further"), 0);
11375         return FALSE;
11376     }
11377     return LoadPosition(lastLoadPositionFP, positionNumber,
11378                         lastLoadPositionTitle);
11379 }
11380
11381 /* Load the nth position from the given file */
11382 int
11383 LoadPositionFromFile(filename, n, title)
11384      char *filename;
11385      int n;
11386      char *title;
11387 {
11388     FILE *f;
11389     char buf[MSG_SIZ];
11390
11391     if (strcmp(filename, "-") == 0) {
11392         return LoadPosition(stdin, n, "stdin");
11393     } else {
11394         f = fopen(filename, "rb");
11395         if (f == NULL) {
11396             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11397             DisplayError(buf, errno);
11398             return FALSE;
11399         } else {
11400             return LoadPosition(f, n, title);
11401         }
11402     }
11403 }
11404
11405 /* Load the nth position from the given open file, and close it */
11406 int
11407 LoadPosition(f, positionNumber, title)
11408      FILE *f;
11409      int positionNumber;
11410      char *title;
11411 {
11412     char *p, line[MSG_SIZ];
11413     Board initial_position;
11414     int i, j, fenMode, pn;
11415
11416     if (gameMode == Training )
11417         SetTrainingModeOff();
11418
11419     if (gameMode != BeginningOfGame) {
11420         Reset(FALSE, TRUE);
11421     }
11422     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11423         fclose(lastLoadPositionFP);
11424     }
11425     if (positionNumber == 0) positionNumber = 1;
11426     lastLoadPositionFP = f;
11427     lastLoadPositionNumber = positionNumber;
11428     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11429     if (first.pr == NoProc) {
11430       StartChessProgram(&first);
11431       InitChessProgram(&first, FALSE);
11432     }
11433     pn = positionNumber;
11434     if (positionNumber < 0) {
11435         /* Negative position number means to seek to that byte offset */
11436         if (fseek(f, -positionNumber, 0) == -1) {
11437             DisplayError(_("Can't seek on position file"), 0);
11438             return FALSE;
11439         };
11440         pn = 1;
11441     } else {
11442         if (fseek(f, 0, 0) == -1) {
11443             if (f == lastLoadPositionFP ?
11444                 positionNumber == lastLoadPositionNumber + 1 :
11445                 positionNumber == 1) {
11446                 pn = 1;
11447             } else {
11448                 DisplayError(_("Can't seek on position file"), 0);
11449                 return FALSE;
11450             }
11451         }
11452     }
11453     /* See if this file is FEN or old-style xboard */
11454     if (fgets(line, MSG_SIZ, f) == NULL) {
11455         DisplayError(_("Position not found in file"), 0);
11456         return FALSE;
11457     }
11458     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11459     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11460
11461     if (pn >= 2) {
11462         if (fenMode || line[0] == '#') pn--;
11463         while (pn > 0) {
11464             /* skip positions before number pn */
11465             if (fgets(line, MSG_SIZ, f) == NULL) {
11466                 Reset(TRUE, TRUE);
11467                 DisplayError(_("Position not found in file"), 0);
11468                 return FALSE;
11469             }
11470             if (fenMode || line[0] == '#') pn--;
11471         }
11472     }
11473
11474     if (fenMode) {
11475         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11476             DisplayError(_("Bad FEN position in file"), 0);
11477             return FALSE;
11478         }
11479     } else {
11480         (void) fgets(line, MSG_SIZ, f);
11481         (void) fgets(line, MSG_SIZ, f);
11482
11483         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11484             (void) fgets(line, MSG_SIZ, f);
11485             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11486                 if (*p == ' ')
11487                   continue;
11488                 initial_position[i][j++] = CharToPiece(*p);
11489             }
11490         }
11491
11492         blackPlaysFirst = FALSE;
11493         if (!feof(f)) {
11494             (void) fgets(line, MSG_SIZ, f);
11495             if (strncmp(line, "black", strlen("black"))==0)
11496               blackPlaysFirst = TRUE;
11497         }
11498     }
11499     startedFromSetupPosition = TRUE;
11500
11501     SendToProgram("force\n", &first);
11502     CopyBoard(boards[0], initial_position);
11503     if (blackPlaysFirst) {
11504         currentMove = forwardMostMove = backwardMostMove = 1;
11505         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11506         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11507         CopyBoard(boards[1], initial_position);
11508         DisplayMessage("", _("Black to play"));
11509     } else {
11510         currentMove = forwardMostMove = backwardMostMove = 0;
11511         DisplayMessage("", _("White to play"));
11512     }
11513     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11514     SendBoard(&first, forwardMostMove);
11515     if (appData.debugMode) {
11516 int i, j;
11517   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11518   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11519         fprintf(debugFP, "Load Position\n");
11520     }
11521
11522     if (positionNumber > 1) {
11523       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11524         DisplayTitle(line);
11525     } else {
11526         DisplayTitle(title);
11527     }
11528     gameMode = EditGame;
11529     ModeHighlight();
11530     ResetClocks();
11531     timeRemaining[0][1] = whiteTimeRemaining;
11532     timeRemaining[1][1] = blackTimeRemaining;
11533     DrawPosition(FALSE, boards[currentMove]);
11534
11535     return TRUE;
11536 }
11537
11538
11539 void
11540 CopyPlayerNameIntoFileName(dest, src)
11541      char **dest, *src;
11542 {
11543     while (*src != NULLCHAR && *src != ',') {
11544         if (*src == ' ') {
11545             *(*dest)++ = '_';
11546             src++;
11547         } else {
11548             *(*dest)++ = *src++;
11549         }
11550     }
11551 }
11552
11553 char *DefaultFileName(ext)
11554      char *ext;
11555 {
11556     static char def[MSG_SIZ];
11557     char *p;
11558
11559     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11560         p = def;
11561         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11562         *p++ = '-';
11563         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11564         *p++ = '.';
11565         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11566     } else {
11567         def[0] = NULLCHAR;
11568     }
11569     return def;
11570 }
11571
11572 /* Save the current game to the given file */
11573 int
11574 SaveGameToFile(filename, append)
11575      char *filename;
11576      int append;
11577 {
11578     FILE *f;
11579     char buf[MSG_SIZ];
11580     int result;
11581
11582     if (strcmp(filename, "-") == 0) {
11583         return SaveGame(stdout, 0, NULL);
11584     } else {
11585         f = fopen(filename, append ? "a" : "w");
11586         if (f == NULL) {
11587             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11588             DisplayError(buf, errno);
11589             return FALSE;
11590         } else {
11591             safeStrCpy(buf, lastMsg, MSG_SIZ);
11592             DisplayMessage(_("Waiting for access to save file"), "");
11593             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11594             DisplayMessage(_("Saving game"), "");
11595             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11596             result = SaveGame(f, 0, NULL);
11597             DisplayMessage(buf, "");
11598             return result;
11599         }
11600     }
11601 }
11602
11603 char *
11604 SavePart(str)
11605      char *str;
11606 {
11607     static char buf[MSG_SIZ];
11608     char *p;
11609
11610     p = strchr(str, ' ');
11611     if (p == NULL) return str;
11612     strncpy(buf, str, p - str);
11613     buf[p - str] = NULLCHAR;
11614     return buf;
11615 }
11616
11617 #define PGN_MAX_LINE 75
11618
11619 #define PGN_SIDE_WHITE  0
11620 #define PGN_SIDE_BLACK  1
11621
11622 /* [AS] */
11623 static int FindFirstMoveOutOfBook( int side )
11624 {
11625     int result = -1;
11626
11627     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11628         int index = backwardMostMove;
11629         int has_book_hit = 0;
11630
11631         if( (index % 2) != side ) {
11632             index++;
11633         }
11634
11635         while( index < forwardMostMove ) {
11636             /* Check to see if engine is in book */
11637             int depth = pvInfoList[index].depth;
11638             int score = pvInfoList[index].score;
11639             int in_book = 0;
11640
11641             if( depth <= 2 ) {
11642                 in_book = 1;
11643             }
11644             else if( score == 0 && depth == 63 ) {
11645                 in_book = 1; /* Zappa */
11646             }
11647             else if( score == 2 && depth == 99 ) {
11648                 in_book = 1; /* Abrok */
11649             }
11650
11651             has_book_hit += in_book;
11652
11653             if( ! in_book ) {
11654                 result = index;
11655
11656                 break;
11657             }
11658
11659             index += 2;
11660         }
11661     }
11662
11663     return result;
11664 }
11665
11666 /* [AS] */
11667 void GetOutOfBookInfo( char * buf )
11668 {
11669     int oob[2];
11670     int i;
11671     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11672
11673     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11674     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11675
11676     *buf = '\0';
11677
11678     if( oob[0] >= 0 || oob[1] >= 0 ) {
11679         for( i=0; i<2; i++ ) {
11680             int idx = oob[i];
11681
11682             if( idx >= 0 ) {
11683                 if( i > 0 && oob[0] >= 0 ) {
11684                     strcat( buf, "   " );
11685                 }
11686
11687                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11688                 sprintf( buf+strlen(buf), "%s%.2f",
11689                     pvInfoList[idx].score >= 0 ? "+" : "",
11690                     pvInfoList[idx].score / 100.0 );
11691             }
11692         }
11693     }
11694 }
11695
11696 /* Save game in PGN style and close the file */
11697 int
11698 SaveGamePGN(f)
11699      FILE *f;
11700 {
11701     int i, offset, linelen, newblock;
11702     time_t tm;
11703 //    char *movetext;
11704     char numtext[32];
11705     int movelen, numlen, blank;
11706     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11707
11708     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11709
11710     tm = time((time_t *) NULL);
11711
11712     PrintPGNTags(f, &gameInfo);
11713
11714     if (backwardMostMove > 0 || startedFromSetupPosition) {
11715         char *fen = PositionToFEN(backwardMostMove, NULL);
11716         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11717         fprintf(f, "\n{--------------\n");
11718         PrintPosition(f, backwardMostMove);
11719         fprintf(f, "--------------}\n");
11720         free(fen);
11721     }
11722     else {
11723         /* [AS] Out of book annotation */
11724         if( appData.saveOutOfBookInfo ) {
11725             char buf[64];
11726
11727             GetOutOfBookInfo( buf );
11728
11729             if( buf[0] != '\0' ) {
11730                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11731             }
11732         }
11733
11734         fprintf(f, "\n");
11735     }
11736
11737     i = backwardMostMove;
11738     linelen = 0;
11739     newblock = TRUE;
11740
11741     while (i < forwardMostMove) {
11742         /* Print comments preceding this move */
11743         if (commentList[i] != NULL) {
11744             if (linelen > 0) fprintf(f, "\n");
11745             fprintf(f, "%s", commentList[i]);
11746             linelen = 0;
11747             newblock = TRUE;
11748         }
11749
11750         /* Format move number */
11751         if ((i % 2) == 0)
11752           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11753         else
11754           if (newblock)
11755             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11756           else
11757             numtext[0] = NULLCHAR;
11758
11759         numlen = strlen(numtext);
11760         newblock = FALSE;
11761
11762         /* Print move number */
11763         blank = linelen > 0 && numlen > 0;
11764         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11765             fprintf(f, "\n");
11766             linelen = 0;
11767             blank = 0;
11768         }
11769         if (blank) {
11770             fprintf(f, " ");
11771             linelen++;
11772         }
11773         fprintf(f, "%s", numtext);
11774         linelen += numlen;
11775
11776         /* Get move */
11777         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11778         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11779
11780         /* Print move */
11781         blank = linelen > 0 && movelen > 0;
11782         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11783             fprintf(f, "\n");
11784             linelen = 0;
11785             blank = 0;
11786         }
11787         if (blank) {
11788             fprintf(f, " ");
11789             linelen++;
11790         }
11791         fprintf(f, "%s", move_buffer);
11792         linelen += movelen;
11793
11794         /* [AS] Add PV info if present */
11795         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11796             /* [HGM] add time */
11797             char buf[MSG_SIZ]; int seconds;
11798
11799             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11800
11801             if( seconds <= 0)
11802               buf[0] = 0;
11803             else
11804               if( seconds < 30 )
11805                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11806               else
11807                 {
11808                   seconds = (seconds + 4)/10; // round to full seconds
11809                   if( seconds < 60 )
11810                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11811                   else
11812                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11813                 }
11814
11815             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11816                       pvInfoList[i].score >= 0 ? "+" : "",
11817                       pvInfoList[i].score / 100.0,
11818                       pvInfoList[i].depth,
11819                       buf );
11820
11821             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11822
11823             /* Print score/depth */
11824             blank = linelen > 0 && movelen > 0;
11825             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11826                 fprintf(f, "\n");
11827                 linelen = 0;
11828                 blank = 0;
11829             }
11830             if (blank) {
11831                 fprintf(f, " ");
11832                 linelen++;
11833             }
11834             fprintf(f, "%s", move_buffer);
11835             linelen += movelen;
11836         }
11837
11838         i++;
11839     }
11840
11841     /* Start a new line */
11842     if (linelen > 0) fprintf(f, "\n");
11843
11844     /* Print comments after last move */
11845     if (commentList[i] != NULL) {
11846         fprintf(f, "%s\n", commentList[i]);
11847     }
11848
11849     /* Print result */
11850     if (gameInfo.resultDetails != NULL &&
11851         gameInfo.resultDetails[0] != NULLCHAR) {
11852         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11853                 PGNResult(gameInfo.result));
11854     } else {
11855         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11856     }
11857
11858     fclose(f);
11859     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11860     return TRUE;
11861 }
11862
11863 /* Save game in old style and close the file */
11864 int
11865 SaveGameOldStyle(f)
11866      FILE *f;
11867 {
11868     int i, offset;
11869     time_t tm;
11870
11871     tm = time((time_t *) NULL);
11872
11873     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11874     PrintOpponents(f);
11875
11876     if (backwardMostMove > 0 || startedFromSetupPosition) {
11877         fprintf(f, "\n[--------------\n");
11878         PrintPosition(f, backwardMostMove);
11879         fprintf(f, "--------------]\n");
11880     } else {
11881         fprintf(f, "\n");
11882     }
11883
11884     i = backwardMostMove;
11885     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11886
11887     while (i < forwardMostMove) {
11888         if (commentList[i] != NULL) {
11889             fprintf(f, "[%s]\n", commentList[i]);
11890         }
11891
11892         if ((i % 2) == 1) {
11893             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11894             i++;
11895         } else {
11896             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11897             i++;
11898             if (commentList[i] != NULL) {
11899                 fprintf(f, "\n");
11900                 continue;
11901             }
11902             if (i >= forwardMostMove) {
11903                 fprintf(f, "\n");
11904                 break;
11905             }
11906             fprintf(f, "%s\n", parseList[i]);
11907             i++;
11908         }
11909     }
11910
11911     if (commentList[i] != NULL) {
11912         fprintf(f, "[%s]\n", commentList[i]);
11913     }
11914
11915     /* This isn't really the old style, but it's close enough */
11916     if (gameInfo.resultDetails != NULL &&
11917         gameInfo.resultDetails[0] != NULLCHAR) {
11918         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11919                 gameInfo.resultDetails);
11920     } else {
11921         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11922     }
11923
11924     fclose(f);
11925     return TRUE;
11926 }
11927
11928 /* Save the current game to open file f and close the file */
11929 int
11930 SaveGame(f, dummy, dummy2)
11931      FILE *f;
11932      int dummy;
11933      char *dummy2;
11934 {
11935     if (gameMode == EditPosition) EditPositionDone(TRUE);
11936     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11937     if (appData.oldSaveStyle)
11938       return SaveGameOldStyle(f);
11939     else
11940       return SaveGamePGN(f);
11941 }
11942
11943 /* Save the current position to the given file */
11944 int
11945 SavePositionToFile(filename)
11946      char *filename;
11947 {
11948     FILE *f;
11949     char buf[MSG_SIZ];
11950
11951     if (strcmp(filename, "-") == 0) {
11952         return SavePosition(stdout, 0, NULL);
11953     } else {
11954         f = fopen(filename, "a");
11955         if (f == NULL) {
11956             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11957             DisplayError(buf, errno);
11958             return FALSE;
11959         } else {
11960             safeStrCpy(buf, lastMsg, MSG_SIZ);
11961             DisplayMessage(_("Waiting for access to save file"), "");
11962             flock(fileno(f), LOCK_EX); // [HGM] lock
11963             DisplayMessage(_("Saving position"), "");
11964             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11965             SavePosition(f, 0, NULL);
11966             DisplayMessage(buf, "");
11967             return TRUE;
11968         }
11969     }
11970 }
11971
11972 /* Save the current position to the given open file and close the file */
11973 int
11974 SavePosition(f, dummy, dummy2)
11975      FILE *f;
11976      int dummy;
11977      char *dummy2;
11978 {
11979     time_t tm;
11980     char *fen;
11981
11982     if (gameMode == EditPosition) EditPositionDone(TRUE);
11983     if (appData.oldSaveStyle) {
11984         tm = time((time_t *) NULL);
11985
11986         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11987         PrintOpponents(f);
11988         fprintf(f, "[--------------\n");
11989         PrintPosition(f, currentMove);
11990         fprintf(f, "--------------]\n");
11991     } else {
11992         fen = PositionToFEN(currentMove, NULL);
11993         fprintf(f, "%s\n", fen);
11994         free(fen);
11995     }
11996     fclose(f);
11997     return TRUE;
11998 }
11999
12000 void
12001 ReloadCmailMsgEvent(unregister)
12002      int unregister;
12003 {
12004 #if !WIN32
12005     static char *inFilename = NULL;
12006     static char *outFilename;
12007     int i;
12008     struct stat inbuf, outbuf;
12009     int status;
12010
12011     /* Any registered moves are unregistered if unregister is set, */
12012     /* i.e. invoked by the signal handler */
12013     if (unregister) {
12014         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12015             cmailMoveRegistered[i] = FALSE;
12016             if (cmailCommentList[i] != NULL) {
12017                 free(cmailCommentList[i]);
12018                 cmailCommentList[i] = NULL;
12019             }
12020         }
12021         nCmailMovesRegistered = 0;
12022     }
12023
12024     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12025         cmailResult[i] = CMAIL_NOT_RESULT;
12026     }
12027     nCmailResults = 0;
12028
12029     if (inFilename == NULL) {
12030         /* Because the filenames are static they only get malloced once  */
12031         /* and they never get freed                                      */
12032         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12033         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12034
12035         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12036         sprintf(outFilename, "%s.out", appData.cmailGameName);
12037     }
12038
12039     status = stat(outFilename, &outbuf);
12040     if (status < 0) {
12041         cmailMailedMove = FALSE;
12042     } else {
12043         status = stat(inFilename, &inbuf);
12044         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12045     }
12046
12047     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12048        counts the games, notes how each one terminated, etc.
12049
12050        It would be nice to remove this kludge and instead gather all
12051        the information while building the game list.  (And to keep it
12052        in the game list nodes instead of having a bunch of fixed-size
12053        parallel arrays.)  Note this will require getting each game's
12054        termination from the PGN tags, as the game list builder does
12055        not process the game moves.  --mann
12056        */
12057     cmailMsgLoaded = TRUE;
12058     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12059
12060     /* Load first game in the file or popup game menu */
12061     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12062
12063 #endif /* !WIN32 */
12064     return;
12065 }
12066
12067 int
12068 RegisterMove()
12069 {
12070     FILE *f;
12071     char string[MSG_SIZ];
12072
12073     if (   cmailMailedMove
12074         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12075         return TRUE;            /* Allow free viewing  */
12076     }
12077
12078     /* Unregister move to ensure that we don't leave RegisterMove        */
12079     /* with the move registered when the conditions for registering no   */
12080     /* longer hold                                                       */
12081     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12082         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12083         nCmailMovesRegistered --;
12084
12085         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12086           {
12087               free(cmailCommentList[lastLoadGameNumber - 1]);
12088               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12089           }
12090     }
12091
12092     if (cmailOldMove == -1) {
12093         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12094         return FALSE;
12095     }
12096
12097     if (currentMove > cmailOldMove + 1) {
12098         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12099         return FALSE;
12100     }
12101
12102     if (currentMove < cmailOldMove) {
12103         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12104         return FALSE;
12105     }
12106
12107     if (forwardMostMove > currentMove) {
12108         /* Silently truncate extra moves */
12109         TruncateGame();
12110     }
12111
12112     if (   (currentMove == cmailOldMove + 1)
12113         || (   (currentMove == cmailOldMove)
12114             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12115                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12116         if (gameInfo.result != GameUnfinished) {
12117             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12118         }
12119
12120         if (commentList[currentMove] != NULL) {
12121             cmailCommentList[lastLoadGameNumber - 1]
12122               = StrSave(commentList[currentMove]);
12123         }
12124         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12125
12126         if (appData.debugMode)
12127           fprintf(debugFP, "Saving %s for game %d\n",
12128                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12129
12130         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12131
12132         f = fopen(string, "w");
12133         if (appData.oldSaveStyle) {
12134             SaveGameOldStyle(f); /* also closes the file */
12135
12136             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12137             f = fopen(string, "w");
12138             SavePosition(f, 0, NULL); /* also closes the file */
12139         } else {
12140             fprintf(f, "{--------------\n");
12141             PrintPosition(f, currentMove);
12142             fprintf(f, "--------------}\n\n");
12143
12144             SaveGame(f, 0, NULL); /* also closes the file*/
12145         }
12146
12147         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12148         nCmailMovesRegistered ++;
12149     } else if (nCmailGames == 1) {
12150         DisplayError(_("You have not made a move yet"), 0);
12151         return FALSE;
12152     }
12153
12154     return TRUE;
12155 }
12156
12157 void
12158 MailMoveEvent()
12159 {
12160 #if !WIN32
12161     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12162     FILE *commandOutput;
12163     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12164     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12165     int nBuffers;
12166     int i;
12167     int archived;
12168     char *arcDir;
12169
12170     if (! cmailMsgLoaded) {
12171         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12172         return;
12173     }
12174
12175     if (nCmailGames == nCmailResults) {
12176         DisplayError(_("No unfinished games"), 0);
12177         return;
12178     }
12179
12180 #if CMAIL_PROHIBIT_REMAIL
12181     if (cmailMailedMove) {
12182       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);
12183         DisplayError(msg, 0);
12184         return;
12185     }
12186 #endif
12187
12188     if (! (cmailMailedMove || RegisterMove())) return;
12189
12190     if (   cmailMailedMove
12191         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12192       snprintf(string, MSG_SIZ, partCommandString,
12193                appData.debugMode ? " -v" : "", appData.cmailGameName);
12194         commandOutput = popen(string, "r");
12195
12196         if (commandOutput == NULL) {
12197             DisplayError(_("Failed to invoke cmail"), 0);
12198         } else {
12199             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12200                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12201             }
12202             if (nBuffers > 1) {
12203                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12204                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12205                 nBytes = MSG_SIZ - 1;
12206             } else {
12207                 (void) memcpy(msg, buffer, nBytes);
12208             }
12209             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12210
12211             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12212                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12213
12214                 archived = TRUE;
12215                 for (i = 0; i < nCmailGames; i ++) {
12216                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12217                         archived = FALSE;
12218                     }
12219                 }
12220                 if (   archived
12221                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12222                         != NULL)) {
12223                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12224                            arcDir,
12225                            appData.cmailGameName,
12226                            gameInfo.date);
12227                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12228                     cmailMsgLoaded = FALSE;
12229                 }
12230             }
12231
12232             DisplayInformation(msg);
12233             pclose(commandOutput);
12234         }
12235     } else {
12236         if ((*cmailMsg) != '\0') {
12237             DisplayInformation(cmailMsg);
12238         }
12239     }
12240
12241     return;
12242 #endif /* !WIN32 */
12243 }
12244
12245 char *
12246 CmailMsg()
12247 {
12248 #if WIN32
12249     return NULL;
12250 #else
12251     int  prependComma = 0;
12252     char number[5];
12253     char string[MSG_SIZ];       /* Space for game-list */
12254     int  i;
12255
12256     if (!cmailMsgLoaded) return "";
12257
12258     if (cmailMailedMove) {
12259       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12260     } else {
12261         /* Create a list of games left */
12262       snprintf(string, MSG_SIZ, "[");
12263         for (i = 0; i < nCmailGames; i ++) {
12264             if (! (   cmailMoveRegistered[i]
12265                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12266                 if (prependComma) {
12267                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12268                 } else {
12269                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12270                     prependComma = 1;
12271                 }
12272
12273                 strcat(string, number);
12274             }
12275         }
12276         strcat(string, "]");
12277
12278         if (nCmailMovesRegistered + nCmailResults == 0) {
12279             switch (nCmailGames) {
12280               case 1:
12281                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12282                 break;
12283
12284               case 2:
12285                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12286                 break;
12287
12288               default:
12289                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12290                          nCmailGames);
12291                 break;
12292             }
12293         } else {
12294             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12295               case 1:
12296                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12297                          string);
12298                 break;
12299
12300               case 0:
12301                 if (nCmailResults == nCmailGames) {
12302                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12303                 } else {
12304                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12305                 }
12306                 break;
12307
12308               default:
12309                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12310                          string);
12311             }
12312         }
12313     }
12314     return cmailMsg;
12315 #endif /* WIN32 */
12316 }
12317
12318 void
12319 ResetGameEvent()
12320 {
12321     if (gameMode == Training)
12322       SetTrainingModeOff();
12323
12324     Reset(TRUE, TRUE);
12325     cmailMsgLoaded = FALSE;
12326     if (appData.icsActive) {
12327       SendToICS(ics_prefix);
12328       SendToICS("refresh\n");
12329     }
12330 }
12331
12332 void
12333 ExitEvent(status)
12334      int status;
12335 {
12336     exiting++;
12337     if (exiting > 2) {
12338       /* Give up on clean exit */
12339       exit(status);
12340     }
12341     if (exiting > 1) {
12342       /* Keep trying for clean exit */
12343       return;
12344     }
12345
12346     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12347
12348     if (telnetISR != NULL) {
12349       RemoveInputSource(telnetISR);
12350     }
12351     if (icsPR != NoProc) {
12352       DestroyChildProcess(icsPR, TRUE);
12353     }
12354
12355     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12356     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12357
12358     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12359     /* make sure this other one finishes before killing it!                  */
12360     if(endingGame) { int count = 0;
12361         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12362         while(endingGame && count++ < 10) DoSleep(1);
12363         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12364     }
12365
12366     /* Kill off chess programs */
12367     if (first.pr != NoProc) {
12368         ExitAnalyzeMode();
12369
12370         DoSleep( appData.delayBeforeQuit );
12371         SendToProgram("quit\n", &first);
12372         DoSleep( appData.delayAfterQuit );
12373         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12374     }
12375     if (second.pr != NoProc) {
12376         DoSleep( appData.delayBeforeQuit );
12377         SendToProgram("quit\n", &second);
12378         DoSleep( appData.delayAfterQuit );
12379         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12380     }
12381     if (first.isr != NULL) {
12382         RemoveInputSource(first.isr);
12383     }
12384     if (second.isr != NULL) {
12385         RemoveInputSource(second.isr);
12386     }
12387
12388     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12389     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12390
12391     ShutDownFrontEnd();
12392     exit(status);
12393 }
12394
12395 void
12396 PauseEvent()
12397 {
12398     if (appData.debugMode)
12399         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12400     if (pausing) {
12401         pausing = FALSE;
12402         ModeHighlight();
12403         if (gameMode == MachinePlaysWhite ||
12404             gameMode == MachinePlaysBlack) {
12405             StartClocks();
12406         } else {
12407             DisplayBothClocks();
12408         }
12409         if (gameMode == PlayFromGameFile) {
12410             if (appData.timeDelay >= 0)
12411                 AutoPlayGameLoop();
12412         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12413             Reset(FALSE, TRUE);
12414             SendToICS(ics_prefix);
12415             SendToICS("refresh\n");
12416         } else if (currentMove < forwardMostMove) {
12417             ForwardInner(forwardMostMove);
12418         }
12419         pauseExamInvalid = FALSE;
12420     } else {
12421         switch (gameMode) {
12422           default:
12423             return;
12424           case IcsExamining:
12425             pauseExamForwardMostMove = forwardMostMove;
12426             pauseExamInvalid = FALSE;
12427             /* fall through */
12428           case IcsObserving:
12429           case IcsPlayingWhite:
12430           case IcsPlayingBlack:
12431             pausing = TRUE;
12432             ModeHighlight();
12433             return;
12434           case PlayFromGameFile:
12435             (void) StopLoadGameTimer();
12436             pausing = TRUE;
12437             ModeHighlight();
12438             break;
12439           case BeginningOfGame:
12440             if (appData.icsActive) return;
12441             /* else fall through */
12442           case MachinePlaysWhite:
12443           case MachinePlaysBlack:
12444           case TwoMachinesPlay:
12445             if (forwardMostMove == 0)
12446               return;           /* don't pause if no one has moved */
12447             if ((gameMode == MachinePlaysWhite &&
12448                  !WhiteOnMove(forwardMostMove)) ||
12449                 (gameMode == MachinePlaysBlack &&
12450                  WhiteOnMove(forwardMostMove))) {
12451                 StopClocks();
12452             }
12453             pausing = TRUE;
12454             ModeHighlight();
12455             break;
12456         }
12457     }
12458 }
12459
12460 void
12461 EditCommentEvent()
12462 {
12463     char title[MSG_SIZ];
12464
12465     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12466       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12467     } else {
12468       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12469                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12470                parseList[currentMove - 1]);
12471     }
12472
12473     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12474 }
12475
12476
12477 void
12478 EditTagsEvent()
12479 {
12480     char *tags = PGNTags(&gameInfo);
12481     bookUp = FALSE;
12482     EditTagsPopUp(tags, NULL);
12483     free(tags);
12484 }
12485
12486 void
12487 AnalyzeModeEvent()
12488 {
12489     if (appData.noChessProgram || gameMode == AnalyzeMode)
12490       return;
12491
12492     if (gameMode != AnalyzeFile) {
12493         if (!appData.icsEngineAnalyze) {
12494                EditGameEvent();
12495                if (gameMode != EditGame) return;
12496         }
12497         ResurrectChessProgram();
12498         SendToProgram("analyze\n", &first);
12499         first.analyzing = TRUE;
12500         /*first.maybeThinking = TRUE;*/
12501         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12502         EngineOutputPopUp();
12503     }
12504     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12505     pausing = FALSE;
12506     ModeHighlight();
12507     SetGameInfo();
12508
12509     StartAnalysisClock();
12510     GetTimeMark(&lastNodeCountTime);
12511     lastNodeCount = 0;
12512 }
12513
12514 void
12515 AnalyzeFileEvent()
12516 {
12517     if (appData.noChessProgram || gameMode == AnalyzeFile)
12518       return;
12519
12520     if (gameMode != AnalyzeMode) {
12521         EditGameEvent();
12522         if (gameMode != EditGame) return;
12523         ResurrectChessProgram();
12524         SendToProgram("analyze\n", &first);
12525         first.analyzing = TRUE;
12526         /*first.maybeThinking = TRUE;*/
12527         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12528         EngineOutputPopUp();
12529     }
12530     gameMode = AnalyzeFile;
12531     pausing = FALSE;
12532     ModeHighlight();
12533     SetGameInfo();
12534
12535     StartAnalysisClock();
12536     GetTimeMark(&lastNodeCountTime);
12537     lastNodeCount = 0;
12538 }
12539
12540 void
12541 MachineWhiteEvent()
12542 {
12543     char buf[MSG_SIZ];
12544     char *bookHit = NULL;
12545
12546     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12547       return;
12548
12549
12550     if (gameMode == PlayFromGameFile ||
12551         gameMode == TwoMachinesPlay  ||
12552         gameMode == Training         ||
12553         gameMode == AnalyzeMode      ||
12554         gameMode == EndOfGame)
12555         EditGameEvent();
12556
12557     if (gameMode == EditPosition)
12558         EditPositionDone(TRUE);
12559
12560     if (!WhiteOnMove(currentMove)) {
12561         DisplayError(_("It is not White's turn"), 0);
12562         return;
12563     }
12564
12565     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12566       ExitAnalyzeMode();
12567
12568     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12569         gameMode == AnalyzeFile)
12570         TruncateGame();
12571
12572     ResurrectChessProgram();    /* in case it isn't running */
12573     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12574         gameMode = MachinePlaysWhite;
12575         ResetClocks();
12576     } else
12577     gameMode = MachinePlaysWhite;
12578     pausing = FALSE;
12579     ModeHighlight();
12580     SetGameInfo();
12581     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12582     DisplayTitle(buf);
12583     if (first.sendName) {
12584       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12585       SendToProgram(buf, &first);
12586     }
12587     if (first.sendTime) {
12588       if (first.useColors) {
12589         SendToProgram("black\n", &first); /*gnu kludge*/
12590       }
12591       SendTimeRemaining(&first, TRUE);
12592     }
12593     if (first.useColors) {
12594       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12595     }
12596     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12597     SetMachineThinkingEnables();
12598     first.maybeThinking = TRUE;
12599     StartClocks();
12600     firstMove = FALSE;
12601
12602     if (appData.autoFlipView && !flipView) {
12603       flipView = !flipView;
12604       DrawPosition(FALSE, NULL);
12605       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12606     }
12607
12608     if(bookHit) { // [HGM] book: simulate book reply
12609         static char bookMove[MSG_SIZ]; // a bit generous?
12610
12611         programStats.nodes = programStats.depth = programStats.time =
12612         programStats.score = programStats.got_only_move = 0;
12613         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12614
12615         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12616         strcat(bookMove, bookHit);
12617         HandleMachineMove(bookMove, &first);
12618     }
12619 }
12620
12621 void
12622 MachineBlackEvent()
12623 {
12624   char buf[MSG_SIZ];
12625   char *bookHit = NULL;
12626
12627     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12628         return;
12629
12630
12631     if (gameMode == PlayFromGameFile ||
12632         gameMode == TwoMachinesPlay  ||
12633         gameMode == Training         ||
12634         gameMode == AnalyzeMode      ||
12635         gameMode == EndOfGame)
12636         EditGameEvent();
12637
12638     if (gameMode == EditPosition)
12639         EditPositionDone(TRUE);
12640
12641     if (WhiteOnMove(currentMove)) {
12642         DisplayError(_("It is not Black's turn"), 0);
12643         return;
12644     }
12645
12646     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12647       ExitAnalyzeMode();
12648
12649     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12650         gameMode == AnalyzeFile)
12651         TruncateGame();
12652
12653     ResurrectChessProgram();    /* in case it isn't running */
12654     gameMode = MachinePlaysBlack;
12655     pausing = FALSE;
12656     ModeHighlight();
12657     SetGameInfo();
12658     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12659     DisplayTitle(buf);
12660     if (first.sendName) {
12661       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12662       SendToProgram(buf, &first);
12663     }
12664     if (first.sendTime) {
12665       if (first.useColors) {
12666         SendToProgram("white\n", &first); /*gnu kludge*/
12667       }
12668       SendTimeRemaining(&first, FALSE);
12669     }
12670     if (first.useColors) {
12671       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12672     }
12673     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12674     SetMachineThinkingEnables();
12675     first.maybeThinking = TRUE;
12676     StartClocks();
12677
12678     if (appData.autoFlipView && flipView) {
12679       flipView = !flipView;
12680       DrawPosition(FALSE, NULL);
12681       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12682     }
12683     if(bookHit) { // [HGM] book: simulate book reply
12684         static char bookMove[MSG_SIZ]; // a bit generous?
12685
12686         programStats.nodes = programStats.depth = programStats.time =
12687         programStats.score = programStats.got_only_move = 0;
12688         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12689
12690         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12691         strcat(bookMove, bookHit);
12692         HandleMachineMove(bookMove, &first);
12693     }
12694 }
12695
12696
12697 void
12698 DisplayTwoMachinesTitle()
12699 {
12700     char buf[MSG_SIZ];
12701     if (appData.matchGames > 0) {
12702         if(appData.tourneyFile[0]) {
12703           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12704                    gameInfo.white, gameInfo.black,
12705                    nextGame+1, appData.matchGames+1,
12706                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12707         } else 
12708         if (first.twoMachinesColor[0] == 'w') {
12709           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12710                    gameInfo.white, gameInfo.black,
12711                    first.matchWins, second.matchWins,
12712                    matchGame - 1 - (first.matchWins + second.matchWins));
12713         } else {
12714           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12715                    gameInfo.white, gameInfo.black,
12716                    second.matchWins, first.matchWins,
12717                    matchGame - 1 - (first.matchWins + second.matchWins));
12718         }
12719     } else {
12720       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12721     }
12722     DisplayTitle(buf);
12723 }
12724
12725 void
12726 SettingsMenuIfReady()
12727 {
12728   if (second.lastPing != second.lastPong) {
12729     DisplayMessage("", _("Waiting for second chess program"));
12730     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12731     return;
12732   }
12733   ThawUI();
12734   DisplayMessage("", "");
12735   SettingsPopUp(&second);
12736 }
12737
12738 int
12739 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12740 {
12741     char buf[MSG_SIZ];
12742     if (cps->pr == NULL) {
12743         StartChessProgram(cps);
12744         if (cps->protocolVersion == 1) {
12745           retry();
12746         } else {
12747           /* kludge: allow timeout for initial "feature" command */
12748           FreezeUI();
12749           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12750           DisplayMessage("", buf);
12751           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12752         }
12753         return 1;
12754     }
12755     return 0;
12756 }
12757
12758 void
12759 TwoMachinesEvent P((void))
12760 {
12761     int i;
12762     char buf[MSG_SIZ];
12763     ChessProgramState *onmove;
12764     char *bookHit = NULL;
12765     static int stalling = 0;
12766     TimeMark now;
12767     long wait;
12768
12769     if (appData.noChessProgram) return;
12770
12771     switch (gameMode) {
12772       case TwoMachinesPlay:
12773         return;
12774       case MachinePlaysWhite:
12775       case MachinePlaysBlack:
12776         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12777             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12778             return;
12779         }
12780         /* fall through */
12781       case BeginningOfGame:
12782       case PlayFromGameFile:
12783       case EndOfGame:
12784         EditGameEvent();
12785         if (gameMode != EditGame) return;
12786         break;
12787       case EditPosition:
12788         EditPositionDone(TRUE);
12789         break;
12790       case AnalyzeMode:
12791       case AnalyzeFile:
12792         ExitAnalyzeMode();
12793         break;
12794       case EditGame:
12795       default:
12796         break;
12797     }
12798
12799 //    forwardMostMove = currentMove;
12800     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12801
12802     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12803
12804     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12805     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12806       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12807       return;
12808     }
12809     if(!stalling) {
12810       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12811       SendToProgram("force\n", &second);
12812       stalling = 1;
12813       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12814       return;
12815     }
12816     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12817     if(appData.matchPause>10000 || appData.matchPause<10)
12818                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12819     wait = SubtractTimeMarks(&now, &pauseStart);
12820     if(wait < appData.matchPause) {
12821         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12822         return;
12823     }
12824     stalling = 0;
12825     DisplayMessage("", "");
12826     if (startedFromSetupPosition) {
12827         SendBoard(&second, backwardMostMove);
12828     if (appData.debugMode) {
12829         fprintf(debugFP, "Two Machines\n");
12830     }
12831     }
12832     for (i = backwardMostMove; i < forwardMostMove; i++) {
12833         SendMoveToProgram(i, &second);
12834     }
12835
12836     gameMode = TwoMachinesPlay;
12837     pausing = FALSE;
12838     ModeHighlight();
12839     SetGameInfo();
12840     DisplayTwoMachinesTitle();
12841     firstMove = TRUE;
12842     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12843         onmove = &first;
12844     } else {
12845         onmove = &second;
12846     }
12847     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12848     SendToProgram(first.computerString, &first);
12849     if (first.sendName) {
12850       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12851       SendToProgram(buf, &first);
12852     }
12853     SendToProgram(second.computerString, &second);
12854     if (second.sendName) {
12855       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12856       SendToProgram(buf, &second);
12857     }
12858
12859     ResetClocks();
12860     if (!first.sendTime || !second.sendTime) {
12861         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12862         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12863     }
12864     if (onmove->sendTime) {
12865       if (onmove->useColors) {
12866         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12867       }
12868       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12869     }
12870     if (onmove->useColors) {
12871       SendToProgram(onmove->twoMachinesColor, onmove);
12872     }
12873     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12874 //    SendToProgram("go\n", onmove);
12875     onmove->maybeThinking = TRUE;
12876     SetMachineThinkingEnables();
12877
12878     StartClocks();
12879
12880     if(bookHit) { // [HGM] book: simulate book reply
12881         static char bookMove[MSG_SIZ]; // a bit generous?
12882
12883         programStats.nodes = programStats.depth = programStats.time =
12884         programStats.score = programStats.got_only_move = 0;
12885         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12886
12887         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12888         strcat(bookMove, bookHit);
12889         savedMessage = bookMove; // args for deferred call
12890         savedState = onmove;
12891         ScheduleDelayedEvent(DeferredBookMove, 1);
12892     }
12893 }
12894
12895 void
12896 TrainingEvent()
12897 {
12898     if (gameMode == Training) {
12899       SetTrainingModeOff();
12900       gameMode = PlayFromGameFile;
12901       DisplayMessage("", _("Training mode off"));
12902     } else {
12903       gameMode = Training;
12904       animateTraining = appData.animate;
12905
12906       /* make sure we are not already at the end of the game */
12907       if (currentMove < forwardMostMove) {
12908         SetTrainingModeOn();
12909         DisplayMessage("", _("Training mode on"));
12910       } else {
12911         gameMode = PlayFromGameFile;
12912         DisplayError(_("Already at end of game"), 0);
12913       }
12914     }
12915     ModeHighlight();
12916 }
12917
12918 void
12919 IcsClientEvent()
12920 {
12921     if (!appData.icsActive) return;
12922     switch (gameMode) {
12923       case IcsPlayingWhite:
12924       case IcsPlayingBlack:
12925       case IcsObserving:
12926       case IcsIdle:
12927       case BeginningOfGame:
12928       case IcsExamining:
12929         return;
12930
12931       case EditGame:
12932         break;
12933
12934       case EditPosition:
12935         EditPositionDone(TRUE);
12936         break;
12937
12938       case AnalyzeMode:
12939       case AnalyzeFile:
12940         ExitAnalyzeMode();
12941         break;
12942
12943       default:
12944         EditGameEvent();
12945         break;
12946     }
12947
12948     gameMode = IcsIdle;
12949     ModeHighlight();
12950     return;
12951 }
12952
12953
12954 void
12955 EditGameEvent()
12956 {
12957     int i;
12958
12959     switch (gameMode) {
12960       case Training:
12961         SetTrainingModeOff();
12962         break;
12963       case MachinePlaysWhite:
12964       case MachinePlaysBlack:
12965       case BeginningOfGame:
12966         SendToProgram("force\n", &first);
12967         SetUserThinkingEnables();
12968         break;
12969       case PlayFromGameFile:
12970         (void) StopLoadGameTimer();
12971         if (gameFileFP != NULL) {
12972             gameFileFP = NULL;
12973         }
12974         break;
12975       case EditPosition:
12976         EditPositionDone(TRUE);
12977         break;
12978       case AnalyzeMode:
12979       case AnalyzeFile:
12980         ExitAnalyzeMode();
12981         SendToProgram("force\n", &first);
12982         break;
12983       case TwoMachinesPlay:
12984         GameEnds(EndOfFile, NULL, GE_PLAYER);
12985         ResurrectChessProgram();
12986         SetUserThinkingEnables();
12987         break;
12988       case EndOfGame:
12989         ResurrectChessProgram();
12990         break;
12991       case IcsPlayingBlack:
12992       case IcsPlayingWhite:
12993         DisplayError(_("Warning: You are still playing a game"), 0);
12994         break;
12995       case IcsObserving:
12996         DisplayError(_("Warning: You are still observing a game"), 0);
12997         break;
12998       case IcsExamining:
12999         DisplayError(_("Warning: You are still examining a game"), 0);
13000         break;
13001       case IcsIdle:
13002         break;
13003       case EditGame:
13004       default:
13005         return;
13006     }
13007
13008     pausing = FALSE;
13009     StopClocks();
13010     first.offeredDraw = second.offeredDraw = 0;
13011
13012     if (gameMode == PlayFromGameFile) {
13013         whiteTimeRemaining = timeRemaining[0][currentMove];
13014         blackTimeRemaining = timeRemaining[1][currentMove];
13015         DisplayTitle("");
13016     }
13017
13018     if (gameMode == MachinePlaysWhite ||
13019         gameMode == MachinePlaysBlack ||
13020         gameMode == TwoMachinesPlay ||
13021         gameMode == EndOfGame) {
13022         i = forwardMostMove;
13023         while (i > currentMove) {
13024             SendToProgram("undo\n", &first);
13025             i--;
13026         }
13027         whiteTimeRemaining = timeRemaining[0][currentMove];
13028         blackTimeRemaining = timeRemaining[1][currentMove];
13029         DisplayBothClocks();
13030         if (whiteFlag || blackFlag) {
13031             whiteFlag = blackFlag = 0;
13032         }
13033         DisplayTitle("");
13034     }
13035
13036     gameMode = EditGame;
13037     ModeHighlight();
13038     SetGameInfo();
13039 }
13040
13041
13042 void
13043 EditPositionEvent()
13044 {
13045     if (gameMode == EditPosition) {
13046         EditGameEvent();
13047         return;
13048     }
13049
13050     EditGameEvent();
13051     if (gameMode != EditGame) return;
13052
13053     gameMode = EditPosition;
13054     ModeHighlight();
13055     SetGameInfo();
13056     if (currentMove > 0)
13057       CopyBoard(boards[0], boards[currentMove]);
13058
13059     blackPlaysFirst = !WhiteOnMove(currentMove);
13060     ResetClocks();
13061     currentMove = forwardMostMove = backwardMostMove = 0;
13062     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13063     DisplayMove(-1);
13064 }
13065
13066 void
13067 ExitAnalyzeMode()
13068 {
13069     /* [DM] icsEngineAnalyze - possible call from other functions */
13070     if (appData.icsEngineAnalyze) {
13071         appData.icsEngineAnalyze = FALSE;
13072
13073         DisplayMessage("",_("Close ICS engine analyze..."));
13074     }
13075     if (first.analysisSupport && first.analyzing) {
13076       SendToProgram("exit\n", &first);
13077       first.analyzing = FALSE;
13078     }
13079     thinkOutput[0] = NULLCHAR;
13080 }
13081
13082 void
13083 EditPositionDone(Boolean fakeRights)
13084 {
13085     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13086
13087     startedFromSetupPosition = TRUE;
13088     InitChessProgram(&first, FALSE);
13089     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13090       boards[0][EP_STATUS] = EP_NONE;
13091       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13092     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13093         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13094         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13095       } else boards[0][CASTLING][2] = NoRights;
13096     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13097         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13098         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13099       } else boards[0][CASTLING][5] = NoRights;
13100     }
13101     SendToProgram("force\n", &first);
13102     if (blackPlaysFirst) {
13103         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13104         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13105         currentMove = forwardMostMove = backwardMostMove = 1;
13106         CopyBoard(boards[1], boards[0]);
13107     } else {
13108         currentMove = forwardMostMove = backwardMostMove = 0;
13109     }
13110     SendBoard(&first, forwardMostMove);
13111     if (appData.debugMode) {
13112         fprintf(debugFP, "EditPosDone\n");
13113     }
13114     DisplayTitle("");
13115     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13116     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13117     gameMode = EditGame;
13118     ModeHighlight();
13119     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13120     ClearHighlights(); /* [AS] */
13121 }
13122
13123 /* Pause for `ms' milliseconds */
13124 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13125 void
13126 TimeDelay(ms)
13127      long ms;
13128 {
13129     TimeMark m1, m2;
13130
13131     GetTimeMark(&m1);
13132     do {
13133         GetTimeMark(&m2);
13134     } while (SubtractTimeMarks(&m2, &m1) < ms);
13135 }
13136
13137 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13138 void
13139 SendMultiLineToICS(buf)
13140      char *buf;
13141 {
13142     char temp[MSG_SIZ+1], *p;
13143     int len;
13144
13145     len = strlen(buf);
13146     if (len > MSG_SIZ)
13147       len = MSG_SIZ;
13148
13149     strncpy(temp, buf, len);
13150     temp[len] = 0;
13151
13152     p = temp;
13153     while (*p) {
13154         if (*p == '\n' || *p == '\r')
13155           *p = ' ';
13156         ++p;
13157     }
13158
13159     strcat(temp, "\n");
13160     SendToICS(temp);
13161     SendToPlayer(temp, strlen(temp));
13162 }
13163
13164 void
13165 SetWhiteToPlayEvent()
13166 {
13167     if (gameMode == EditPosition) {
13168         blackPlaysFirst = FALSE;
13169         DisplayBothClocks();    /* works because currentMove is 0 */
13170     } else if (gameMode == IcsExamining) {
13171         SendToICS(ics_prefix);
13172         SendToICS("tomove white\n");
13173     }
13174 }
13175
13176 void
13177 SetBlackToPlayEvent()
13178 {
13179     if (gameMode == EditPosition) {
13180         blackPlaysFirst = TRUE;
13181         currentMove = 1;        /* kludge */
13182         DisplayBothClocks();
13183         currentMove = 0;
13184     } else if (gameMode == IcsExamining) {
13185         SendToICS(ics_prefix);
13186         SendToICS("tomove black\n");
13187     }
13188 }
13189
13190 void
13191 EditPositionMenuEvent(selection, x, y)
13192      ChessSquare selection;
13193      int x, y;
13194 {
13195     char buf[MSG_SIZ];
13196     ChessSquare piece = boards[0][y][x];
13197
13198     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13199
13200     switch (selection) {
13201       case ClearBoard:
13202         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13203             SendToICS(ics_prefix);
13204             SendToICS("bsetup clear\n");
13205         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13206             SendToICS(ics_prefix);
13207             SendToICS("clearboard\n");
13208         } else {
13209             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13210                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13211                 for (y = 0; y < BOARD_HEIGHT; y++) {
13212                     if (gameMode == IcsExamining) {
13213                         if (boards[currentMove][y][x] != EmptySquare) {
13214                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13215                                     AAA + x, ONE + y);
13216                             SendToICS(buf);
13217                         }
13218                     } else {
13219                         boards[0][y][x] = p;
13220                     }
13221                 }
13222             }
13223         }
13224         if (gameMode == EditPosition) {
13225             DrawPosition(FALSE, boards[0]);
13226         }
13227         break;
13228
13229       case WhitePlay:
13230         SetWhiteToPlayEvent();
13231         break;
13232
13233       case BlackPlay:
13234         SetBlackToPlayEvent();
13235         break;
13236
13237       case EmptySquare:
13238         if (gameMode == IcsExamining) {
13239             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13240             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13241             SendToICS(buf);
13242         } else {
13243             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13244                 if(x == BOARD_LEFT-2) {
13245                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13246                     boards[0][y][1] = 0;
13247                 } else
13248                 if(x == BOARD_RGHT+1) {
13249                     if(y >= gameInfo.holdingsSize) break;
13250                     boards[0][y][BOARD_WIDTH-2] = 0;
13251                 } else break;
13252             }
13253             boards[0][y][x] = EmptySquare;
13254             DrawPosition(FALSE, boards[0]);
13255         }
13256         break;
13257
13258       case PromotePiece:
13259         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13260            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13261             selection = (ChessSquare) (PROMOTED piece);
13262         } else if(piece == EmptySquare) selection = WhiteSilver;
13263         else selection = (ChessSquare)((int)piece - 1);
13264         goto defaultlabel;
13265
13266       case DemotePiece:
13267         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13268            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13269             selection = (ChessSquare) (DEMOTED piece);
13270         } else if(piece == EmptySquare) selection = BlackSilver;
13271         else selection = (ChessSquare)((int)piece + 1);
13272         goto defaultlabel;
13273
13274       case WhiteQueen:
13275       case BlackQueen:
13276         if(gameInfo.variant == VariantShatranj ||
13277            gameInfo.variant == VariantXiangqi  ||
13278            gameInfo.variant == VariantCourier  ||
13279            gameInfo.variant == VariantMakruk     )
13280             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13281         goto defaultlabel;
13282
13283       case WhiteKing:
13284       case BlackKing:
13285         if(gameInfo.variant == VariantXiangqi)
13286             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13287         if(gameInfo.variant == VariantKnightmate)
13288             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13289       default:
13290         defaultlabel:
13291         if (gameMode == IcsExamining) {
13292             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13293             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13294                      PieceToChar(selection), AAA + x, ONE + y);
13295             SendToICS(buf);
13296         } else {
13297             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13298                 int n;
13299                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13300                     n = PieceToNumber(selection - BlackPawn);
13301                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13302                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13303                     boards[0][BOARD_HEIGHT-1-n][1]++;
13304                 } else
13305                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13306                     n = PieceToNumber(selection);
13307                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13308                     boards[0][n][BOARD_WIDTH-1] = selection;
13309                     boards[0][n][BOARD_WIDTH-2]++;
13310                 }
13311             } else
13312             boards[0][y][x] = selection;
13313             DrawPosition(TRUE, boards[0]);
13314         }
13315         break;
13316     }
13317 }
13318
13319
13320 void
13321 DropMenuEvent(selection, x, y)
13322      ChessSquare selection;
13323      int x, y;
13324 {
13325     ChessMove moveType;
13326
13327     switch (gameMode) {
13328       case IcsPlayingWhite:
13329       case MachinePlaysBlack:
13330         if (!WhiteOnMove(currentMove)) {
13331             DisplayMoveError(_("It is Black's turn"));
13332             return;
13333         }
13334         moveType = WhiteDrop;
13335         break;
13336       case IcsPlayingBlack:
13337       case MachinePlaysWhite:
13338         if (WhiteOnMove(currentMove)) {
13339             DisplayMoveError(_("It is White's turn"));
13340             return;
13341         }
13342         moveType = BlackDrop;
13343         break;
13344       case EditGame:
13345         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13346         break;
13347       default:
13348         return;
13349     }
13350
13351     if (moveType == BlackDrop && selection < BlackPawn) {
13352       selection = (ChessSquare) ((int) selection
13353                                  + (int) BlackPawn - (int) WhitePawn);
13354     }
13355     if (boards[currentMove][y][x] != EmptySquare) {
13356         DisplayMoveError(_("That square is occupied"));
13357         return;
13358     }
13359
13360     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13361 }
13362
13363 void
13364 AcceptEvent()
13365 {
13366     /* Accept a pending offer of any kind from opponent */
13367
13368     if (appData.icsActive) {
13369         SendToICS(ics_prefix);
13370         SendToICS("accept\n");
13371     } else if (cmailMsgLoaded) {
13372         if (currentMove == cmailOldMove &&
13373             commentList[cmailOldMove] != NULL &&
13374             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13375                    "Black offers a draw" : "White offers a draw")) {
13376             TruncateGame();
13377             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13378             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13379         } else {
13380             DisplayError(_("There is no pending offer on this move"), 0);
13381             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13382         }
13383     } else {
13384         /* Not used for offers from chess program */
13385     }
13386 }
13387
13388 void
13389 DeclineEvent()
13390 {
13391     /* Decline a pending offer of any kind from opponent */
13392
13393     if (appData.icsActive) {
13394         SendToICS(ics_prefix);
13395         SendToICS("decline\n");
13396     } else if (cmailMsgLoaded) {
13397         if (currentMove == cmailOldMove &&
13398             commentList[cmailOldMove] != NULL &&
13399             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13400                    "Black offers a draw" : "White offers a draw")) {
13401 #ifdef NOTDEF
13402             AppendComment(cmailOldMove, "Draw declined", TRUE);
13403             DisplayComment(cmailOldMove - 1, "Draw declined");
13404 #endif /*NOTDEF*/
13405         } else {
13406             DisplayError(_("There is no pending offer on this move"), 0);
13407         }
13408     } else {
13409         /* Not used for offers from chess program */
13410     }
13411 }
13412
13413 void
13414 RematchEvent()
13415 {
13416     /* Issue ICS rematch command */
13417     if (appData.icsActive) {
13418         SendToICS(ics_prefix);
13419         SendToICS("rematch\n");
13420     }
13421 }
13422
13423 void
13424 CallFlagEvent()
13425 {
13426     /* Call your opponent's flag (claim a win on time) */
13427     if (appData.icsActive) {
13428         SendToICS(ics_prefix);
13429         SendToICS("flag\n");
13430     } else {
13431         switch (gameMode) {
13432           default:
13433             return;
13434           case MachinePlaysWhite:
13435             if (whiteFlag) {
13436                 if (blackFlag)
13437                   GameEnds(GameIsDrawn, "Both players ran out of time",
13438                            GE_PLAYER);
13439                 else
13440                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13441             } else {
13442                 DisplayError(_("Your opponent is not out of time"), 0);
13443             }
13444             break;
13445           case MachinePlaysBlack:
13446             if (blackFlag) {
13447                 if (whiteFlag)
13448                   GameEnds(GameIsDrawn, "Both players ran out of time",
13449                            GE_PLAYER);
13450                 else
13451                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13452             } else {
13453                 DisplayError(_("Your opponent is not out of time"), 0);
13454             }
13455             break;
13456         }
13457     }
13458 }
13459
13460 void
13461 ClockClick(int which)
13462 {       // [HGM] code moved to back-end from winboard.c
13463         if(which) { // black clock
13464           if (gameMode == EditPosition || gameMode == IcsExamining) {
13465             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13466             SetBlackToPlayEvent();
13467           } else if (gameMode == EditGame || shiftKey) {
13468             AdjustClock(which, -1);
13469           } else if (gameMode == IcsPlayingWhite ||
13470                      gameMode == MachinePlaysBlack) {
13471             CallFlagEvent();
13472           }
13473         } else { // white clock
13474           if (gameMode == EditPosition || gameMode == IcsExamining) {
13475             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13476             SetWhiteToPlayEvent();
13477           } else if (gameMode == EditGame || shiftKey) {
13478             AdjustClock(which, -1);
13479           } else if (gameMode == IcsPlayingBlack ||
13480                    gameMode == MachinePlaysWhite) {
13481             CallFlagEvent();
13482           }
13483         }
13484 }
13485
13486 void
13487 DrawEvent()
13488 {
13489     /* Offer draw or accept pending draw offer from opponent */
13490
13491     if (appData.icsActive) {
13492         /* Note: tournament rules require draw offers to be
13493            made after you make your move but before you punch
13494            your clock.  Currently ICS doesn't let you do that;
13495            instead, you immediately punch your clock after making
13496            a move, but you can offer a draw at any time. */
13497
13498         SendToICS(ics_prefix);
13499         SendToICS("draw\n");
13500         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13501     } else if (cmailMsgLoaded) {
13502         if (currentMove == cmailOldMove &&
13503             commentList[cmailOldMove] != NULL &&
13504             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13505                    "Black offers a draw" : "White offers a draw")) {
13506             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13507             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13508         } else if (currentMove == cmailOldMove + 1) {
13509             char *offer = WhiteOnMove(cmailOldMove) ?
13510               "White offers a draw" : "Black offers a draw";
13511             AppendComment(currentMove, offer, TRUE);
13512             DisplayComment(currentMove - 1, offer);
13513             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13514         } else {
13515             DisplayError(_("You must make your move before offering a draw"), 0);
13516             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13517         }
13518     } else if (first.offeredDraw) {
13519         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13520     } else {
13521         if (first.sendDrawOffers) {
13522             SendToProgram("draw\n", &first);
13523             userOfferedDraw = TRUE;
13524         }
13525     }
13526 }
13527
13528 void
13529 AdjournEvent()
13530 {
13531     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13532
13533     if (appData.icsActive) {
13534         SendToICS(ics_prefix);
13535         SendToICS("adjourn\n");
13536     } else {
13537         /* Currently GNU Chess doesn't offer or accept Adjourns */
13538     }
13539 }
13540
13541
13542 void
13543 AbortEvent()
13544 {
13545     /* Offer Abort or accept pending Abort offer from opponent */
13546
13547     if (appData.icsActive) {
13548         SendToICS(ics_prefix);
13549         SendToICS("abort\n");
13550     } else {
13551         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13552     }
13553 }
13554
13555 void
13556 ResignEvent()
13557 {
13558     /* Resign.  You can do this even if it's not your turn. */
13559
13560     if (appData.icsActive) {
13561         SendToICS(ics_prefix);
13562         SendToICS("resign\n");
13563     } else {
13564         switch (gameMode) {
13565           case MachinePlaysWhite:
13566             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13567             break;
13568           case MachinePlaysBlack:
13569             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13570             break;
13571           case EditGame:
13572             if (cmailMsgLoaded) {
13573                 TruncateGame();
13574                 if (WhiteOnMove(cmailOldMove)) {
13575                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13576                 } else {
13577                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13578                 }
13579                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13580             }
13581             break;
13582           default:
13583             break;
13584         }
13585     }
13586 }
13587
13588
13589 void
13590 StopObservingEvent()
13591 {
13592     /* Stop observing current games */
13593     SendToICS(ics_prefix);
13594     SendToICS("unobserve\n");
13595 }
13596
13597 void
13598 StopExaminingEvent()
13599 {
13600     /* Stop observing current game */
13601     SendToICS(ics_prefix);
13602     SendToICS("unexamine\n");
13603 }
13604
13605 void
13606 ForwardInner(target)
13607      int target;
13608 {
13609     int limit;
13610
13611     if (appData.debugMode)
13612         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13613                 target, currentMove, forwardMostMove);
13614
13615     if (gameMode == EditPosition)
13616       return;
13617
13618     if (gameMode == PlayFromGameFile && !pausing)
13619       PauseEvent();
13620
13621     if (gameMode == IcsExamining && pausing)
13622       limit = pauseExamForwardMostMove;
13623     else
13624       limit = forwardMostMove;
13625
13626     if (target > limit) target = limit;
13627
13628     if (target > 0 && moveList[target - 1][0]) {
13629         int fromX, fromY, toX, toY;
13630         toX = moveList[target - 1][2] - AAA;
13631         toY = moveList[target - 1][3] - ONE;
13632         if (moveList[target - 1][1] == '@') {
13633             if (appData.highlightLastMove) {
13634                 SetHighlights(-1, -1, toX, toY);
13635             }
13636         } else {
13637             fromX = moveList[target - 1][0] - AAA;
13638             fromY = moveList[target - 1][1] - ONE;
13639             if (target == currentMove + 1) {
13640                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13641             }
13642             if (appData.highlightLastMove) {
13643                 SetHighlights(fromX, fromY, toX, toY);
13644             }
13645         }
13646     }
13647     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13648         gameMode == Training || gameMode == PlayFromGameFile ||
13649         gameMode == AnalyzeFile) {
13650         while (currentMove < target) {
13651             SendMoveToProgram(currentMove++, &first);
13652         }
13653     } else {
13654         currentMove = target;
13655     }
13656
13657     if (gameMode == EditGame || gameMode == EndOfGame) {
13658         whiteTimeRemaining = timeRemaining[0][currentMove];
13659         blackTimeRemaining = timeRemaining[1][currentMove];
13660     }
13661     DisplayBothClocks();
13662     DisplayMove(currentMove - 1);
13663     DrawPosition(FALSE, boards[currentMove]);
13664     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13665     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13666         DisplayComment(currentMove - 1, commentList[currentMove]);
13667     }
13668     DisplayBook(currentMove);
13669 }
13670
13671
13672 void
13673 ForwardEvent()
13674 {
13675     if (gameMode == IcsExamining && !pausing) {
13676         SendToICS(ics_prefix);
13677         SendToICS("forward\n");
13678     } else {
13679         ForwardInner(currentMove + 1);
13680     }
13681 }
13682
13683 void
13684 ToEndEvent()
13685 {
13686     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13687         /* to optimze, we temporarily turn off analysis mode while we feed
13688          * the remaining moves to the engine. Otherwise we get analysis output
13689          * after each move.
13690          */
13691         if (first.analysisSupport) {
13692           SendToProgram("exit\nforce\n", &first);
13693           first.analyzing = FALSE;
13694         }
13695     }
13696
13697     if (gameMode == IcsExamining && !pausing) {
13698         SendToICS(ics_prefix);
13699         SendToICS("forward 999999\n");
13700     } else {
13701         ForwardInner(forwardMostMove);
13702     }
13703
13704     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13705         /* we have fed all the moves, so reactivate analysis mode */
13706         SendToProgram("analyze\n", &first);
13707         first.analyzing = TRUE;
13708         /*first.maybeThinking = TRUE;*/
13709         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13710     }
13711 }
13712
13713 void
13714 BackwardInner(target)
13715      int target;
13716 {
13717     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13718
13719     if (appData.debugMode)
13720         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13721                 target, currentMove, forwardMostMove);
13722
13723     if (gameMode == EditPosition) return;
13724     if (currentMove <= backwardMostMove) {
13725         ClearHighlights();
13726         DrawPosition(full_redraw, boards[currentMove]);
13727         return;
13728     }
13729     if (gameMode == PlayFromGameFile && !pausing)
13730       PauseEvent();
13731
13732     if (moveList[target][0]) {
13733         int fromX, fromY, toX, toY;
13734         toX = moveList[target][2] - AAA;
13735         toY = moveList[target][3] - ONE;
13736         if (moveList[target][1] == '@') {
13737             if (appData.highlightLastMove) {
13738                 SetHighlights(-1, -1, toX, toY);
13739             }
13740         } else {
13741             fromX = moveList[target][0] - AAA;
13742             fromY = moveList[target][1] - ONE;
13743             if (target == currentMove - 1) {
13744                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13745             }
13746             if (appData.highlightLastMove) {
13747                 SetHighlights(fromX, fromY, toX, toY);
13748             }
13749         }
13750     }
13751     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13752         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13753         while (currentMove > target) {
13754             SendToProgram("undo\n", &first);
13755             currentMove--;
13756         }
13757     } else {
13758         currentMove = target;
13759     }
13760
13761     if (gameMode == EditGame || gameMode == EndOfGame) {
13762         whiteTimeRemaining = timeRemaining[0][currentMove];
13763         blackTimeRemaining = timeRemaining[1][currentMove];
13764     }
13765     DisplayBothClocks();
13766     DisplayMove(currentMove - 1);
13767     DrawPosition(full_redraw, boards[currentMove]);
13768     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13769     // [HGM] PV info: routine tests if comment empty
13770     DisplayComment(currentMove - 1, commentList[currentMove]);
13771     DisplayBook(currentMove);
13772 }
13773
13774 void
13775 BackwardEvent()
13776 {
13777     if (gameMode == IcsExamining && !pausing) {
13778         SendToICS(ics_prefix);
13779         SendToICS("backward\n");
13780     } else {
13781         BackwardInner(currentMove - 1);
13782     }
13783 }
13784
13785 void
13786 ToStartEvent()
13787 {
13788     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13789         /* to optimize, we temporarily turn off analysis mode while we undo
13790          * all the moves. Otherwise we get analysis output after each undo.
13791          */
13792         if (first.analysisSupport) {
13793           SendToProgram("exit\nforce\n", &first);
13794           first.analyzing = FALSE;
13795         }
13796     }
13797
13798     if (gameMode == IcsExamining && !pausing) {
13799         SendToICS(ics_prefix);
13800         SendToICS("backward 999999\n");
13801     } else {
13802         BackwardInner(backwardMostMove);
13803     }
13804
13805     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13806         /* we have fed all the moves, so reactivate analysis mode */
13807         SendToProgram("analyze\n", &first);
13808         first.analyzing = TRUE;
13809         /*first.maybeThinking = TRUE;*/
13810         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13811     }
13812 }
13813
13814 void
13815 ToNrEvent(int to)
13816 {
13817   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13818   if (to >= forwardMostMove) to = forwardMostMove;
13819   if (to <= backwardMostMove) to = backwardMostMove;
13820   if (to < currentMove) {
13821     BackwardInner(to);
13822   } else {
13823     ForwardInner(to);
13824   }
13825 }
13826
13827 void
13828 RevertEvent(Boolean annotate)
13829 {
13830     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13831         return;
13832     }
13833     if (gameMode != IcsExamining) {
13834         DisplayError(_("You are not examining a game"), 0);
13835         return;
13836     }
13837     if (pausing) {
13838         DisplayError(_("You can't revert while pausing"), 0);
13839         return;
13840     }
13841     SendToICS(ics_prefix);
13842     SendToICS("revert\n");
13843 }
13844
13845 void
13846 RetractMoveEvent()
13847 {
13848     switch (gameMode) {
13849       case MachinePlaysWhite:
13850       case MachinePlaysBlack:
13851         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13852             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13853             return;
13854         }
13855         if (forwardMostMove < 2) return;
13856         currentMove = forwardMostMove = forwardMostMove - 2;
13857         whiteTimeRemaining = timeRemaining[0][currentMove];
13858         blackTimeRemaining = timeRemaining[1][currentMove];
13859         DisplayBothClocks();
13860         DisplayMove(currentMove - 1);
13861         ClearHighlights();/*!! could figure this out*/
13862         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13863         SendToProgram("remove\n", &first);
13864         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13865         break;
13866
13867       case BeginningOfGame:
13868       default:
13869         break;
13870
13871       case IcsPlayingWhite:
13872       case IcsPlayingBlack:
13873         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13874             SendToICS(ics_prefix);
13875             SendToICS("takeback 2\n");
13876         } else {
13877             SendToICS(ics_prefix);
13878             SendToICS("takeback 1\n");
13879         }
13880         break;
13881     }
13882 }
13883
13884 void
13885 MoveNowEvent()
13886 {
13887     ChessProgramState *cps;
13888
13889     switch (gameMode) {
13890       case MachinePlaysWhite:
13891         if (!WhiteOnMove(forwardMostMove)) {
13892             DisplayError(_("It is your turn"), 0);
13893             return;
13894         }
13895         cps = &first;
13896         break;
13897       case MachinePlaysBlack:
13898         if (WhiteOnMove(forwardMostMove)) {
13899             DisplayError(_("It is your turn"), 0);
13900             return;
13901         }
13902         cps = &first;
13903         break;
13904       case TwoMachinesPlay:
13905         if (WhiteOnMove(forwardMostMove) ==
13906             (first.twoMachinesColor[0] == 'w')) {
13907             cps = &first;
13908         } else {
13909             cps = &second;
13910         }
13911         break;
13912       case BeginningOfGame:
13913       default:
13914         return;
13915     }
13916     SendToProgram("?\n", cps);
13917 }
13918
13919 void
13920 TruncateGameEvent()
13921 {
13922     EditGameEvent();
13923     if (gameMode != EditGame) return;
13924     TruncateGame();
13925 }
13926
13927 void
13928 TruncateGame()
13929 {
13930     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13931     if (forwardMostMove > currentMove) {
13932         if (gameInfo.resultDetails != NULL) {
13933             free(gameInfo.resultDetails);
13934             gameInfo.resultDetails = NULL;
13935             gameInfo.result = GameUnfinished;
13936         }
13937         forwardMostMove = currentMove;
13938         HistorySet(parseList, backwardMostMove, forwardMostMove,
13939                    currentMove-1);
13940     }
13941 }
13942
13943 void
13944 HintEvent()
13945 {
13946     if (appData.noChessProgram) return;
13947     switch (gameMode) {
13948       case MachinePlaysWhite:
13949         if (WhiteOnMove(forwardMostMove)) {
13950             DisplayError(_("Wait until your turn"), 0);
13951             return;
13952         }
13953         break;
13954       case BeginningOfGame:
13955       case MachinePlaysBlack:
13956         if (!WhiteOnMove(forwardMostMove)) {
13957             DisplayError(_("Wait until your turn"), 0);
13958             return;
13959         }
13960         break;
13961       default:
13962         DisplayError(_("No hint available"), 0);
13963         return;
13964     }
13965     SendToProgram("hint\n", &first);
13966     hintRequested = TRUE;
13967 }
13968
13969 void
13970 BookEvent()
13971 {
13972     if (appData.noChessProgram) return;
13973     switch (gameMode) {
13974       case MachinePlaysWhite:
13975         if (WhiteOnMove(forwardMostMove)) {
13976             DisplayError(_("Wait until your turn"), 0);
13977             return;
13978         }
13979         break;
13980       case BeginningOfGame:
13981       case MachinePlaysBlack:
13982         if (!WhiteOnMove(forwardMostMove)) {
13983             DisplayError(_("Wait until your turn"), 0);
13984             return;
13985         }
13986         break;
13987       case EditPosition:
13988         EditPositionDone(TRUE);
13989         break;
13990       case TwoMachinesPlay:
13991         return;
13992       default:
13993         break;
13994     }
13995     SendToProgram("bk\n", &first);
13996     bookOutput[0] = NULLCHAR;
13997     bookRequested = TRUE;
13998 }
13999
14000 void
14001 AboutGameEvent()
14002 {
14003     char *tags = PGNTags(&gameInfo);
14004     TagsPopUp(tags, CmailMsg());
14005     free(tags);
14006 }
14007
14008 /* end button procedures */
14009
14010 void
14011 PrintPosition(fp, move)
14012      FILE *fp;
14013      int move;
14014 {
14015     int i, j;
14016
14017     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14018         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14019             char c = PieceToChar(boards[move][i][j]);
14020             fputc(c == 'x' ? '.' : c, fp);
14021             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14022         }
14023     }
14024     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14025       fprintf(fp, "white to play\n");
14026     else
14027       fprintf(fp, "black to play\n");
14028 }
14029
14030 void
14031 PrintOpponents(fp)
14032      FILE *fp;
14033 {
14034     if (gameInfo.white != NULL) {
14035         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14036     } else {
14037         fprintf(fp, "\n");
14038     }
14039 }
14040
14041 /* Find last component of program's own name, using some heuristics */
14042 void
14043 TidyProgramName(prog, host, buf)
14044      char *prog, *host, buf[MSG_SIZ];
14045 {
14046     char *p, *q;
14047     int local = (strcmp(host, "localhost") == 0);
14048     while (!local && (p = strchr(prog, ';')) != NULL) {
14049         p++;
14050         while (*p == ' ') p++;
14051         prog = p;
14052     }
14053     if (*prog == '"' || *prog == '\'') {
14054         q = strchr(prog + 1, *prog);
14055     } else {
14056         q = strchr(prog, ' ');
14057     }
14058     if (q == NULL) q = prog + strlen(prog);
14059     p = q;
14060     while (p >= prog && *p != '/' && *p != '\\') p--;
14061     p++;
14062     if(p == prog && *p == '"') p++;
14063     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14064     memcpy(buf, p, q - p);
14065     buf[q - p] = NULLCHAR;
14066     if (!local) {
14067         strcat(buf, "@");
14068         strcat(buf, host);
14069     }
14070 }
14071
14072 char *
14073 TimeControlTagValue()
14074 {
14075     char buf[MSG_SIZ];
14076     if (!appData.clockMode) {
14077       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14078     } else if (movesPerSession > 0) {
14079       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14080     } else if (timeIncrement == 0) {
14081       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14082     } else {
14083       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14084     }
14085     return StrSave(buf);
14086 }
14087
14088 void
14089 SetGameInfo()
14090 {
14091     /* This routine is used only for certain modes */
14092     VariantClass v = gameInfo.variant;
14093     ChessMove r = GameUnfinished;
14094     char *p = NULL;
14095
14096     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14097         r = gameInfo.result;
14098         p = gameInfo.resultDetails;
14099         gameInfo.resultDetails = NULL;
14100     }
14101     ClearGameInfo(&gameInfo);
14102     gameInfo.variant = v;
14103
14104     switch (gameMode) {
14105       case MachinePlaysWhite:
14106         gameInfo.event = StrSave( appData.pgnEventHeader );
14107         gameInfo.site = StrSave(HostName());
14108         gameInfo.date = PGNDate();
14109         gameInfo.round = StrSave("-");
14110         gameInfo.white = StrSave(first.tidy);
14111         gameInfo.black = StrSave(UserName());
14112         gameInfo.timeControl = TimeControlTagValue();
14113         break;
14114
14115       case MachinePlaysBlack:
14116         gameInfo.event = StrSave( appData.pgnEventHeader );
14117         gameInfo.site = StrSave(HostName());
14118         gameInfo.date = PGNDate();
14119         gameInfo.round = StrSave("-");
14120         gameInfo.white = StrSave(UserName());
14121         gameInfo.black = StrSave(first.tidy);
14122         gameInfo.timeControl = TimeControlTagValue();
14123         break;
14124
14125       case TwoMachinesPlay:
14126         gameInfo.event = StrSave( appData.pgnEventHeader );
14127         gameInfo.site = StrSave(HostName());
14128         gameInfo.date = PGNDate();
14129         if (roundNr > 0) {
14130             char buf[MSG_SIZ];
14131             snprintf(buf, MSG_SIZ, "%d", roundNr);
14132             gameInfo.round = StrSave(buf);
14133         } else {
14134             gameInfo.round = StrSave("-");
14135         }
14136         if (first.twoMachinesColor[0] == 'w') {
14137             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14138             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14139         } else {
14140             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14141             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14142         }
14143         gameInfo.timeControl = TimeControlTagValue();
14144         break;
14145
14146       case EditGame:
14147         gameInfo.event = StrSave("Edited game");
14148         gameInfo.site = StrSave(HostName());
14149         gameInfo.date = PGNDate();
14150         gameInfo.round = StrSave("-");
14151         gameInfo.white = StrSave("-");
14152         gameInfo.black = StrSave("-");
14153         gameInfo.result = r;
14154         gameInfo.resultDetails = p;
14155         break;
14156
14157       case EditPosition:
14158         gameInfo.event = StrSave("Edited position");
14159         gameInfo.site = StrSave(HostName());
14160         gameInfo.date = PGNDate();
14161         gameInfo.round = StrSave("-");
14162         gameInfo.white = StrSave("-");
14163         gameInfo.black = StrSave("-");
14164         break;
14165
14166       case IcsPlayingWhite:
14167       case IcsPlayingBlack:
14168       case IcsObserving:
14169       case IcsExamining:
14170         break;
14171
14172       case PlayFromGameFile:
14173         gameInfo.event = StrSave("Game from non-PGN file");
14174         gameInfo.site = StrSave(HostName());
14175         gameInfo.date = PGNDate();
14176         gameInfo.round = StrSave("-");
14177         gameInfo.white = StrSave("?");
14178         gameInfo.black = StrSave("?");
14179         break;
14180
14181       default:
14182         break;
14183     }
14184 }
14185
14186 void
14187 ReplaceComment(index, text)
14188      int index;
14189      char *text;
14190 {
14191     int len;
14192     char *p;
14193     float score;
14194
14195     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14196        pvInfoList[index-1].depth == len &&
14197        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14198        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14199     while (*text == '\n') text++;
14200     len = strlen(text);
14201     while (len > 0 && text[len - 1] == '\n') len--;
14202
14203     if (commentList[index] != NULL)
14204       free(commentList[index]);
14205
14206     if (len == 0) {
14207         commentList[index] = NULL;
14208         return;
14209     }
14210   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14211       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14212       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14213     commentList[index] = (char *) malloc(len + 2);
14214     strncpy(commentList[index], text, len);
14215     commentList[index][len] = '\n';
14216     commentList[index][len + 1] = NULLCHAR;
14217   } else {
14218     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14219     char *p;
14220     commentList[index] = (char *) malloc(len + 7);
14221     safeStrCpy(commentList[index], "{\n", 3);
14222     safeStrCpy(commentList[index]+2, text, len+1);
14223     commentList[index][len+2] = NULLCHAR;
14224     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14225     strcat(commentList[index], "\n}\n");
14226   }
14227 }
14228
14229 void
14230 CrushCRs(text)
14231      char *text;
14232 {
14233   char *p = text;
14234   char *q = text;
14235   char ch;
14236
14237   do {
14238     ch = *p++;
14239     if (ch == '\r') continue;
14240     *q++ = ch;
14241   } while (ch != '\0');
14242 }
14243
14244 void
14245 AppendComment(index, text, addBraces)
14246      int index;
14247      char *text;
14248      Boolean addBraces; // [HGM] braces: tells if we should add {}
14249 {
14250     int oldlen, len;
14251     char *old;
14252
14253 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14254     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14255
14256     CrushCRs(text);
14257     while (*text == '\n') text++;
14258     len = strlen(text);
14259     while (len > 0 && text[len - 1] == '\n') len--;
14260
14261     if (len == 0) return;
14262
14263     if (commentList[index] != NULL) {
14264         old = commentList[index];
14265         oldlen = strlen(old);
14266         while(commentList[index][oldlen-1] ==  '\n')
14267           commentList[index][--oldlen] = NULLCHAR;
14268         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14269         safeStrCpy(commentList[index], old, oldlen + len + 6);
14270         free(old);
14271         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14272         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14273           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14274           while (*text == '\n') { text++; len--; }
14275           commentList[index][--oldlen] = NULLCHAR;
14276       }
14277         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14278         else          strcat(commentList[index], "\n");
14279         strcat(commentList[index], text);
14280         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14281         else          strcat(commentList[index], "\n");
14282     } else {
14283         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14284         if(addBraces)
14285           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14286         else commentList[index][0] = NULLCHAR;
14287         strcat(commentList[index], text);
14288         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14289         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14290     }
14291 }
14292
14293 static char * FindStr( char * text, char * sub_text )
14294 {
14295     char * result = strstr( text, sub_text );
14296
14297     if( result != NULL ) {
14298         result += strlen( sub_text );
14299     }
14300
14301     return result;
14302 }
14303
14304 /* [AS] Try to extract PV info from PGN comment */
14305 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14306 char *GetInfoFromComment( int index, char * text )
14307 {
14308     char * sep = text, *p;
14309
14310     if( text != NULL && index > 0 ) {
14311         int score = 0;
14312         int depth = 0;
14313         int time = -1, sec = 0, deci;
14314         char * s_eval = FindStr( text, "[%eval " );
14315         char * s_emt = FindStr( text, "[%emt " );
14316
14317         if( s_eval != NULL || s_emt != NULL ) {
14318             /* New style */
14319             char delim;
14320
14321             if( s_eval != NULL ) {
14322                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14323                     return text;
14324                 }
14325
14326                 if( delim != ']' ) {
14327                     return text;
14328                 }
14329             }
14330
14331             if( s_emt != NULL ) {
14332             }
14333                 return text;
14334         }
14335         else {
14336             /* We expect something like: [+|-]nnn.nn/dd */
14337             int score_lo = 0;
14338
14339             if(*text != '{') return text; // [HGM] braces: must be normal comment
14340
14341             sep = strchr( text, '/' );
14342             if( sep == NULL || sep < (text+4) ) {
14343                 return text;
14344             }
14345
14346             p = text;
14347             if(p[1] == '(') { // comment starts with PV
14348                p = strchr(p, ')'); // locate end of PV
14349                if(p == NULL || sep < p+5) return text;
14350                // at this point we have something like "{(.*) +0.23/6 ..."
14351                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14352                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14353                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14354             }
14355             time = -1; sec = -1; deci = -1;
14356             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14357                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14358                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14359                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14360                 return text;
14361             }
14362
14363             if( score_lo < 0 || score_lo >= 100 ) {
14364                 return text;
14365             }
14366
14367             if(sec >= 0) time = 600*time + 10*sec; else
14368             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14369
14370             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14371
14372             /* [HGM] PV time: now locate end of PV info */
14373             while( *++sep >= '0' && *sep <= '9'); // strip depth
14374             if(time >= 0)
14375             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14376             if(sec >= 0)
14377             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14378             if(deci >= 0)
14379             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14380             while(*sep == ' ') sep++;
14381         }
14382
14383         if( depth <= 0 ) {
14384             return text;
14385         }
14386
14387         if( time < 0 ) {
14388             time = -1;
14389         }
14390
14391         pvInfoList[index-1].depth = depth;
14392         pvInfoList[index-1].score = score;
14393         pvInfoList[index-1].time  = 10*time; // centi-sec
14394         if(*sep == '}') *sep = 0; else *--sep = '{';
14395         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14396     }
14397     return sep;
14398 }
14399
14400 void
14401 SendToProgram(message, cps)
14402      char *message;
14403      ChessProgramState *cps;
14404 {
14405     int count, outCount, error;
14406     char buf[MSG_SIZ];
14407
14408     if (cps->pr == NULL) return;
14409     Attention(cps);
14410
14411     if (appData.debugMode) {
14412         TimeMark now;
14413         GetTimeMark(&now);
14414         fprintf(debugFP, "%ld >%-6s: %s",
14415                 SubtractTimeMarks(&now, &programStartTime),
14416                 cps->which, message);
14417     }
14418
14419     count = strlen(message);
14420     outCount = OutputToProcess(cps->pr, message, count, &error);
14421     if (outCount < count && !exiting
14422                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14423       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14424       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14425         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14426             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14427                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14428                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14429                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14430             } else {
14431                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14432                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14433                 gameInfo.result = res;
14434             }
14435             gameInfo.resultDetails = StrSave(buf);
14436         }
14437         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14438         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14439     }
14440 }
14441
14442 void
14443 ReceiveFromProgram(isr, closure, message, count, error)
14444      InputSourceRef isr;
14445      VOIDSTAR closure;
14446      char *message;
14447      int count;
14448      int error;
14449 {
14450     char *end_str;
14451     char buf[MSG_SIZ];
14452     ChessProgramState *cps = (ChessProgramState *)closure;
14453
14454     if (isr != cps->isr) return; /* Killed intentionally */
14455     if (count <= 0) {
14456         if (count == 0) {
14457             RemoveInputSource(cps->isr);
14458             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14459             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14460                     _(cps->which), cps->program);
14461         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14462                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14463                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14464                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14465                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14466                 } else {
14467                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14468                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14469                     gameInfo.result = res;
14470                 }
14471                 gameInfo.resultDetails = StrSave(buf);
14472             }
14473             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14474             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14475         } else {
14476             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14477                     _(cps->which), cps->program);
14478             RemoveInputSource(cps->isr);
14479
14480             /* [AS] Program is misbehaving badly... kill it */
14481             if( count == -2 ) {
14482                 DestroyChildProcess( cps->pr, 9 );
14483                 cps->pr = NoProc;
14484             }
14485
14486             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14487         }
14488         return;
14489     }
14490
14491     if ((end_str = strchr(message, '\r')) != NULL)
14492       *end_str = NULLCHAR;
14493     if ((end_str = strchr(message, '\n')) != NULL)
14494       *end_str = NULLCHAR;
14495
14496     if (appData.debugMode) {
14497         TimeMark now; int print = 1;
14498         char *quote = ""; char c; int i;
14499
14500         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14501                 char start = message[0];
14502                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14503                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14504                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14505                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14506                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14507                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14508                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14509                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14510                    sscanf(message, "hint: %c", &c)!=1 && 
14511                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14512                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14513                     print = (appData.engineComments >= 2);
14514                 }
14515                 message[0] = start; // restore original message
14516         }
14517         if(print) {
14518                 GetTimeMark(&now);
14519                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14520                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14521                         quote,
14522                         message);
14523         }
14524     }
14525
14526     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14527     if (appData.icsEngineAnalyze) {
14528         if (strstr(message, "whisper") != NULL ||
14529              strstr(message, "kibitz") != NULL ||
14530             strstr(message, "tellics") != NULL) return;
14531     }
14532
14533     HandleMachineMove(message, cps);
14534 }
14535
14536
14537 void
14538 SendTimeControl(cps, mps, tc, inc, sd, st)
14539      ChessProgramState *cps;
14540      int mps, inc, sd, st;
14541      long tc;
14542 {
14543     char buf[MSG_SIZ];
14544     int seconds;
14545
14546     if( timeControl_2 > 0 ) {
14547         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14548             tc = timeControl_2;
14549         }
14550     }
14551     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14552     inc /= cps->timeOdds;
14553     st  /= cps->timeOdds;
14554
14555     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14556
14557     if (st > 0) {
14558       /* Set exact time per move, normally using st command */
14559       if (cps->stKludge) {
14560         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14561         seconds = st % 60;
14562         if (seconds == 0) {
14563           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14564         } else {
14565           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14566         }
14567       } else {
14568         snprintf(buf, MSG_SIZ, "st %d\n", st);
14569       }
14570     } else {
14571       /* Set conventional or incremental time control, using level command */
14572       if (seconds == 0) {
14573         /* Note old gnuchess bug -- minutes:seconds used to not work.
14574            Fixed in later versions, but still avoid :seconds
14575            when seconds is 0. */
14576         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14577       } else {
14578         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14579                  seconds, inc/1000.);
14580       }
14581     }
14582     SendToProgram(buf, cps);
14583
14584     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14585     /* Orthogonally, limit search to given depth */
14586     if (sd > 0) {
14587       if (cps->sdKludge) {
14588         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14589       } else {
14590         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14591       }
14592       SendToProgram(buf, cps);
14593     }
14594
14595     if(cps->nps >= 0) { /* [HGM] nps */
14596         if(cps->supportsNPS == FALSE)
14597           cps->nps = -1; // don't use if engine explicitly says not supported!
14598         else {
14599           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14600           SendToProgram(buf, cps);
14601         }
14602     }
14603 }
14604
14605 ChessProgramState *WhitePlayer()
14606 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14607 {
14608     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14609        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14610         return &second;
14611     return &first;
14612 }
14613
14614 void
14615 SendTimeRemaining(cps, machineWhite)
14616      ChessProgramState *cps;
14617      int /*boolean*/ machineWhite;
14618 {
14619     char message[MSG_SIZ];
14620     long time, otime;
14621
14622     /* Note: this routine must be called when the clocks are stopped
14623        or when they have *just* been set or switched; otherwise
14624        it will be off by the time since the current tick started.
14625     */
14626     if (machineWhite) {
14627         time = whiteTimeRemaining / 10;
14628         otime = blackTimeRemaining / 10;
14629     } else {
14630         time = blackTimeRemaining / 10;
14631         otime = whiteTimeRemaining / 10;
14632     }
14633     /* [HGM] translate opponent's time by time-odds factor */
14634     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14635     if (appData.debugMode) {
14636         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14637     }
14638
14639     if (time <= 0) time = 1;
14640     if (otime <= 0) otime = 1;
14641
14642     snprintf(message, MSG_SIZ, "time %ld\n", time);
14643     SendToProgram(message, cps);
14644
14645     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14646     SendToProgram(message, cps);
14647 }
14648
14649 int
14650 BoolFeature(p, name, loc, cps)
14651      char **p;
14652      char *name;
14653      int *loc;
14654      ChessProgramState *cps;
14655 {
14656   char buf[MSG_SIZ];
14657   int len = strlen(name);
14658   int val;
14659
14660   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14661     (*p) += len + 1;
14662     sscanf(*p, "%d", &val);
14663     *loc = (val != 0);
14664     while (**p && **p != ' ')
14665       (*p)++;
14666     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14667     SendToProgram(buf, cps);
14668     return TRUE;
14669   }
14670   return FALSE;
14671 }
14672
14673 int
14674 IntFeature(p, name, loc, cps)
14675      char **p;
14676      char *name;
14677      int *loc;
14678      ChessProgramState *cps;
14679 {
14680   char buf[MSG_SIZ];
14681   int len = strlen(name);
14682   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14683     (*p) += len + 1;
14684     sscanf(*p, "%d", loc);
14685     while (**p && **p != ' ') (*p)++;
14686     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14687     SendToProgram(buf, cps);
14688     return TRUE;
14689   }
14690   return FALSE;
14691 }
14692
14693 int
14694 StringFeature(p, name, loc, cps)
14695      char **p;
14696      char *name;
14697      char loc[];
14698      ChessProgramState *cps;
14699 {
14700   char buf[MSG_SIZ];
14701   int len = strlen(name);
14702   if (strncmp((*p), name, len) == 0
14703       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14704     (*p) += len + 2;
14705     sscanf(*p, "%[^\"]", loc);
14706     while (**p && **p != '\"') (*p)++;
14707     if (**p == '\"') (*p)++;
14708     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14709     SendToProgram(buf, cps);
14710     return TRUE;
14711   }
14712   return FALSE;
14713 }
14714
14715 int
14716 ParseOption(Option *opt, ChessProgramState *cps)
14717 // [HGM] options: process the string that defines an engine option, and determine
14718 // name, type, default value, and allowed value range
14719 {
14720         char *p, *q, buf[MSG_SIZ];
14721         int n, min = (-1)<<31, max = 1<<31, def;
14722
14723         if(p = strstr(opt->name, " -spin ")) {
14724             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14725             if(max < min) max = min; // enforce consistency
14726             if(def < min) def = min;
14727             if(def > max) def = max;
14728             opt->value = def;
14729             opt->min = min;
14730             opt->max = max;
14731             opt->type = Spin;
14732         } else if((p = strstr(opt->name, " -slider "))) {
14733             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14734             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14735             if(max < min) max = min; // enforce consistency
14736             if(def < min) def = min;
14737             if(def > max) def = max;
14738             opt->value = def;
14739             opt->min = min;
14740             opt->max = max;
14741             opt->type = Spin; // Slider;
14742         } else if((p = strstr(opt->name, " -string "))) {
14743             opt->textValue = p+9;
14744             opt->type = TextBox;
14745         } else if((p = strstr(opt->name, " -file "))) {
14746             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14747             opt->textValue = p+7;
14748             opt->type = FileName; // FileName;
14749         } else if((p = strstr(opt->name, " -path "))) {
14750             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14751             opt->textValue = p+7;
14752             opt->type = PathName; // PathName;
14753         } else if(p = strstr(opt->name, " -check ")) {
14754             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14755             opt->value = (def != 0);
14756             opt->type = CheckBox;
14757         } else if(p = strstr(opt->name, " -combo ")) {
14758             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14759             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14760             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14761             opt->value = n = 0;
14762             while(q = StrStr(q, " /// ")) {
14763                 n++; *q = 0;    // count choices, and null-terminate each of them
14764                 q += 5;
14765                 if(*q == '*') { // remember default, which is marked with * prefix
14766                     q++;
14767                     opt->value = n;
14768                 }
14769                 cps->comboList[cps->comboCnt++] = q;
14770             }
14771             cps->comboList[cps->comboCnt++] = NULL;
14772             opt->max = n + 1;
14773             opt->type = ComboBox;
14774         } else if(p = strstr(opt->name, " -button")) {
14775             opt->type = Button;
14776         } else if(p = strstr(opt->name, " -save")) {
14777             opt->type = SaveButton;
14778         } else return FALSE;
14779         *p = 0; // terminate option name
14780         // now look if the command-line options define a setting for this engine option.
14781         if(cps->optionSettings && cps->optionSettings[0])
14782             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14783         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14784           snprintf(buf, MSG_SIZ, "option %s", p);
14785                 if(p = strstr(buf, ",")) *p = 0;
14786                 if(q = strchr(buf, '=')) switch(opt->type) {
14787                     case ComboBox:
14788                         for(n=0; n<opt->max; n++)
14789                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14790                         break;
14791                     case TextBox:
14792                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14793                         break;
14794                     case Spin:
14795                     case CheckBox:
14796                         opt->value = atoi(q+1);
14797                     default:
14798                         break;
14799                 }
14800                 strcat(buf, "\n");
14801                 SendToProgram(buf, cps);
14802         }
14803         return TRUE;
14804 }
14805
14806 void
14807 FeatureDone(cps, val)
14808      ChessProgramState* cps;
14809      int val;
14810 {
14811   DelayedEventCallback cb = GetDelayedEvent();
14812   if ((cb == InitBackEnd3 && cps == &first) ||
14813       (cb == SettingsMenuIfReady && cps == &second) ||
14814       (cb == LoadEngine) ||
14815       (cb == TwoMachinesEventIfReady)) {
14816     CancelDelayedEvent();
14817     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14818   }
14819   cps->initDone = val;
14820 }
14821
14822 /* Parse feature command from engine */
14823 void
14824 ParseFeatures(args, cps)
14825      char* args;
14826      ChessProgramState *cps;
14827 {
14828   char *p = args;
14829   char *q;
14830   int val;
14831   char buf[MSG_SIZ];
14832
14833   for (;;) {
14834     while (*p == ' ') p++;
14835     if (*p == NULLCHAR) return;
14836
14837     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14838     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14839     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14840     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14841     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14842     if (BoolFeature(&p, "reuse", &val, cps)) {
14843       /* Engine can disable reuse, but can't enable it if user said no */
14844       if (!val) cps->reuse = FALSE;
14845       continue;
14846     }
14847     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14848     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14849       if (gameMode == TwoMachinesPlay) {
14850         DisplayTwoMachinesTitle();
14851       } else {
14852         DisplayTitle("");
14853       }
14854       continue;
14855     }
14856     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14857     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14858     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14859     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14860     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14861     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14862     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14863     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14864     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14865     if (IntFeature(&p, "done", &val, cps)) {
14866       FeatureDone(cps, val);
14867       continue;
14868     }
14869     /* Added by Tord: */
14870     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14871     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14872     /* End of additions by Tord */
14873
14874     /* [HGM] added features: */
14875     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14876     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14877     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14878     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14879     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14880     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14881     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14882         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14883           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14884             SendToProgram(buf, cps);
14885             continue;
14886         }
14887         if(cps->nrOptions >= MAX_OPTIONS) {
14888             cps->nrOptions--;
14889             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14890             DisplayError(buf, 0);
14891         }
14892         continue;
14893     }
14894     /* End of additions by HGM */
14895
14896     /* unknown feature: complain and skip */
14897     q = p;
14898     while (*q && *q != '=') q++;
14899     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14900     SendToProgram(buf, cps);
14901     p = q;
14902     if (*p == '=') {
14903       p++;
14904       if (*p == '\"') {
14905         p++;
14906         while (*p && *p != '\"') p++;
14907         if (*p == '\"') p++;
14908       } else {
14909         while (*p && *p != ' ') p++;
14910       }
14911     }
14912   }
14913
14914 }
14915
14916 void
14917 PeriodicUpdatesEvent(newState)
14918      int newState;
14919 {
14920     if (newState == appData.periodicUpdates)
14921       return;
14922
14923     appData.periodicUpdates=newState;
14924
14925     /* Display type changes, so update it now */
14926 //    DisplayAnalysis();
14927
14928     /* Get the ball rolling again... */
14929     if (newState) {
14930         AnalysisPeriodicEvent(1);
14931         StartAnalysisClock();
14932     }
14933 }
14934
14935 void
14936 PonderNextMoveEvent(newState)
14937      int newState;
14938 {
14939     if (newState == appData.ponderNextMove) return;
14940     if (gameMode == EditPosition) EditPositionDone(TRUE);
14941     if (newState) {
14942         SendToProgram("hard\n", &first);
14943         if (gameMode == TwoMachinesPlay) {
14944             SendToProgram("hard\n", &second);
14945         }
14946     } else {
14947         SendToProgram("easy\n", &first);
14948         thinkOutput[0] = NULLCHAR;
14949         if (gameMode == TwoMachinesPlay) {
14950             SendToProgram("easy\n", &second);
14951         }
14952     }
14953     appData.ponderNextMove = newState;
14954 }
14955
14956 void
14957 NewSettingEvent(option, feature, command, value)
14958      char *command;
14959      int option, value, *feature;
14960 {
14961     char buf[MSG_SIZ];
14962
14963     if (gameMode == EditPosition) EditPositionDone(TRUE);
14964     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14965     if(feature == NULL || *feature) SendToProgram(buf, &first);
14966     if (gameMode == TwoMachinesPlay) {
14967         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14968     }
14969 }
14970
14971 void
14972 ShowThinkingEvent()
14973 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14974 {
14975     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14976     int newState = appData.showThinking
14977         // [HGM] thinking: other features now need thinking output as well
14978         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14979
14980     if (oldState == newState) return;
14981     oldState = newState;
14982     if (gameMode == EditPosition) EditPositionDone(TRUE);
14983     if (oldState) {
14984         SendToProgram("post\n", &first);
14985         if (gameMode == TwoMachinesPlay) {
14986             SendToProgram("post\n", &second);
14987         }
14988     } else {
14989         SendToProgram("nopost\n", &first);
14990         thinkOutput[0] = NULLCHAR;
14991         if (gameMode == TwoMachinesPlay) {
14992             SendToProgram("nopost\n", &second);
14993         }
14994     }
14995 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14996 }
14997
14998 void
14999 AskQuestionEvent(title, question, replyPrefix, which)
15000      char *title; char *question; char *replyPrefix; char *which;
15001 {
15002   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15003   if (pr == NoProc) return;
15004   AskQuestion(title, question, replyPrefix, pr);
15005 }
15006
15007 void
15008 TypeInEvent(char firstChar)
15009 {
15010     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15011         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15012         gameMode == AnalyzeMode || gameMode == EditGame || \r
15013         gameMode == EditPosition || gameMode == IcsExamining ||\r
15014         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15015         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15016                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15017                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15018         gameMode == Training) PopUpMoveDialog(firstChar);
15019 }
15020
15021 void
15022 TypeInDoneEvent(char *move)
15023 {
15024         Board board;
15025         int n, fromX, fromY, toX, toY;
15026         char promoChar;
15027         ChessMove moveType;\r
15028
15029         // [HGM] FENedit\r
15030         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15031                 EditPositionPasteFEN(move);\r
15032                 return;\r
15033         }\r
15034         // [HGM] movenum: allow move number to be typed in any mode\r
15035         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15036           ToNrEvent(2*n-1);\r
15037           return;\r
15038         }\r
15039
15040       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15041         gameMode != Training) {\r
15042         DisplayMoveError(_("Displayed move is not current"));\r
15043       } else {\r
15044         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15045           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15046         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15047         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15048           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15049           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15050         } else {\r
15051           DisplayMoveError(_("Could not parse move"));\r
15052         }
15053       }\r
15054 }\r
15055
15056 void
15057 DisplayMove(moveNumber)
15058      int moveNumber;
15059 {
15060     char message[MSG_SIZ];
15061     char res[MSG_SIZ];
15062     char cpThinkOutput[MSG_SIZ];
15063
15064     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15065
15066     if (moveNumber == forwardMostMove - 1 ||
15067         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15068
15069         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15070
15071         if (strchr(cpThinkOutput, '\n')) {
15072             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15073         }
15074     } else {
15075         *cpThinkOutput = NULLCHAR;
15076     }
15077
15078     /* [AS] Hide thinking from human user */
15079     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15080         *cpThinkOutput = NULLCHAR;
15081         if( thinkOutput[0] != NULLCHAR ) {
15082             int i;
15083
15084             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15085                 cpThinkOutput[i] = '.';
15086             }
15087             cpThinkOutput[i] = NULLCHAR;
15088             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15089         }
15090     }
15091
15092     if (moveNumber == forwardMostMove - 1 &&
15093         gameInfo.resultDetails != NULL) {
15094         if (gameInfo.resultDetails[0] == NULLCHAR) {
15095           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15096         } else {
15097           snprintf(res, MSG_SIZ, " {%s} %s",
15098                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15099         }
15100     } else {
15101         res[0] = NULLCHAR;
15102     }
15103
15104     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15105         DisplayMessage(res, cpThinkOutput);
15106     } else {
15107       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15108                 WhiteOnMove(moveNumber) ? " " : ".. ",
15109                 parseList[moveNumber], res);
15110         DisplayMessage(message, cpThinkOutput);
15111     }
15112 }
15113
15114 void
15115 DisplayComment(moveNumber, text)
15116      int moveNumber;
15117      char *text;
15118 {
15119     char title[MSG_SIZ];
15120     char buf[8000]; // comment can be long!
15121     int score, depth;
15122
15123     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15124       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15125     } else {
15126       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15127               WhiteOnMove(moveNumber) ? " " : ".. ",
15128               parseList[moveNumber]);
15129     }
15130     // [HGM] PV info: display PV info together with (or as) comment
15131     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15132       if(text == NULL) text = "";
15133       score = pvInfoList[moveNumber].score;
15134       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15135               depth, (pvInfoList[moveNumber].time+50)/100, text);
15136       text = buf;
15137     }
15138     if (text != NULL && (appData.autoDisplayComment || commentUp))
15139         CommentPopUp(title, text);
15140 }
15141
15142 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15143  * might be busy thinking or pondering.  It can be omitted if your
15144  * gnuchess is configured to stop thinking immediately on any user
15145  * input.  However, that gnuchess feature depends on the FIONREAD
15146  * ioctl, which does not work properly on some flavors of Unix.
15147  */
15148 void
15149 Attention(cps)
15150      ChessProgramState *cps;
15151 {
15152 #if ATTENTION
15153     if (!cps->useSigint) return;
15154     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15155     switch (gameMode) {
15156       case MachinePlaysWhite:
15157       case MachinePlaysBlack:
15158       case TwoMachinesPlay:
15159       case IcsPlayingWhite:
15160       case IcsPlayingBlack:
15161       case AnalyzeMode:
15162       case AnalyzeFile:
15163         /* Skip if we know it isn't thinking */
15164         if (!cps->maybeThinking) return;
15165         if (appData.debugMode)
15166           fprintf(debugFP, "Interrupting %s\n", cps->which);
15167         InterruptChildProcess(cps->pr);
15168         cps->maybeThinking = FALSE;
15169         break;
15170       default:
15171         break;
15172     }
15173 #endif /*ATTENTION*/
15174 }
15175
15176 int
15177 CheckFlags()
15178 {
15179     if (whiteTimeRemaining <= 0) {
15180         if (!whiteFlag) {
15181             whiteFlag = TRUE;
15182             if (appData.icsActive) {
15183                 if (appData.autoCallFlag &&
15184                     gameMode == IcsPlayingBlack && !blackFlag) {
15185                   SendToICS(ics_prefix);
15186                   SendToICS("flag\n");
15187                 }
15188             } else {
15189                 if (blackFlag) {
15190                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15191                 } else {
15192                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15193                     if (appData.autoCallFlag) {
15194                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15195                         return TRUE;
15196                     }
15197                 }
15198             }
15199         }
15200     }
15201     if (blackTimeRemaining <= 0) {
15202         if (!blackFlag) {
15203             blackFlag = TRUE;
15204             if (appData.icsActive) {
15205                 if (appData.autoCallFlag &&
15206                     gameMode == IcsPlayingWhite && !whiteFlag) {
15207                   SendToICS(ics_prefix);
15208                   SendToICS("flag\n");
15209                 }
15210             } else {
15211                 if (whiteFlag) {
15212                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15213                 } else {
15214                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15215                     if (appData.autoCallFlag) {
15216                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15217                         return TRUE;
15218                     }
15219                 }
15220             }
15221         }
15222     }
15223     return FALSE;
15224 }
15225
15226 void
15227 CheckTimeControl()
15228 {
15229     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15230         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15231
15232     /*
15233      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15234      */
15235     if ( !WhiteOnMove(forwardMostMove) ) {
15236         /* White made time control */
15237         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15238         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15239         /* [HGM] time odds: correct new time quota for time odds! */
15240                                             / WhitePlayer()->timeOdds;
15241         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15242     } else {
15243         lastBlack -= blackTimeRemaining;
15244         /* Black made time control */
15245         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15246                                             / WhitePlayer()->other->timeOdds;
15247         lastWhite = whiteTimeRemaining;
15248     }
15249 }
15250
15251 void
15252 DisplayBothClocks()
15253 {
15254     int wom = gameMode == EditPosition ?
15255       !blackPlaysFirst : WhiteOnMove(currentMove);
15256     DisplayWhiteClock(whiteTimeRemaining, wom);
15257     DisplayBlackClock(blackTimeRemaining, !wom);
15258 }
15259
15260
15261 /* Timekeeping seems to be a portability nightmare.  I think everyone
15262    has ftime(), but I'm really not sure, so I'm including some ifdefs
15263    to use other calls if you don't.  Clocks will be less accurate if
15264    you have neither ftime nor gettimeofday.
15265 */
15266
15267 /* VS 2008 requires the #include outside of the function */
15268 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15269 #include <sys/timeb.h>
15270 #endif
15271
15272 /* Get the current time as a TimeMark */
15273 void
15274 GetTimeMark(tm)
15275      TimeMark *tm;
15276 {
15277 #if HAVE_GETTIMEOFDAY
15278
15279     struct timeval timeVal;
15280     struct timezone timeZone;
15281
15282     gettimeofday(&timeVal, &timeZone);
15283     tm->sec = (long) timeVal.tv_sec;
15284     tm->ms = (int) (timeVal.tv_usec / 1000L);
15285
15286 #else /*!HAVE_GETTIMEOFDAY*/
15287 #if HAVE_FTIME
15288
15289 // include <sys/timeb.h> / moved to just above start of function
15290     struct timeb timeB;
15291
15292     ftime(&timeB);
15293     tm->sec = (long) timeB.time;
15294     tm->ms = (int) timeB.millitm;
15295
15296 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15297     tm->sec = (long) time(NULL);
15298     tm->ms = 0;
15299 #endif
15300 #endif
15301 }
15302
15303 /* Return the difference in milliseconds between two
15304    time marks.  We assume the difference will fit in a long!
15305 */
15306 long
15307 SubtractTimeMarks(tm2, tm1)
15308      TimeMark *tm2, *tm1;
15309 {
15310     return 1000L*(tm2->sec - tm1->sec) +
15311            (long) (tm2->ms - tm1->ms);
15312 }
15313
15314
15315 /*
15316  * Code to manage the game clocks.
15317  *
15318  * In tournament play, black starts the clock and then white makes a move.
15319  * We give the human user a slight advantage if he is playing white---the
15320  * clocks don't run until he makes his first move, so it takes zero time.
15321  * Also, we don't account for network lag, so we could get out of sync
15322  * with GNU Chess's clock -- but then, referees are always right.
15323  */
15324
15325 static TimeMark tickStartTM;
15326 static long intendedTickLength;
15327
15328 long
15329 NextTickLength(timeRemaining)
15330      long timeRemaining;
15331 {
15332     long nominalTickLength, nextTickLength;
15333
15334     if (timeRemaining > 0L && timeRemaining <= 10000L)
15335       nominalTickLength = 100L;
15336     else
15337       nominalTickLength = 1000L;
15338     nextTickLength = timeRemaining % nominalTickLength;
15339     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15340
15341     return nextTickLength;
15342 }
15343
15344 /* Adjust clock one minute up or down */
15345 void
15346 AdjustClock(Boolean which, int dir)
15347 {
15348     if(which) blackTimeRemaining += 60000*dir;
15349     else      whiteTimeRemaining += 60000*dir;
15350     DisplayBothClocks();
15351 }
15352
15353 /* Stop clocks and reset to a fresh time control */
15354 void
15355 ResetClocks()
15356 {
15357     (void) StopClockTimer();
15358     if (appData.icsActive) {
15359         whiteTimeRemaining = blackTimeRemaining = 0;
15360     } else if (searchTime) {
15361         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15362         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15363     } else { /* [HGM] correct new time quote for time odds */
15364         whiteTC = blackTC = fullTimeControlString;
15365         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15366         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15367     }
15368     if (whiteFlag || blackFlag) {
15369         DisplayTitle("");
15370         whiteFlag = blackFlag = FALSE;
15371     }
15372     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15373     DisplayBothClocks();
15374 }
15375
15376 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15377
15378 /* Decrement running clock by amount of time that has passed */
15379 void
15380 DecrementClocks()
15381 {
15382     long timeRemaining;
15383     long lastTickLength, fudge;
15384     TimeMark now;
15385
15386     if (!appData.clockMode) return;
15387     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15388
15389     GetTimeMark(&now);
15390
15391     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15392
15393     /* Fudge if we woke up a little too soon */
15394     fudge = intendedTickLength - lastTickLength;
15395     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15396
15397     if (WhiteOnMove(forwardMostMove)) {
15398         if(whiteNPS >= 0) lastTickLength = 0;
15399         timeRemaining = whiteTimeRemaining -= lastTickLength;
15400         if(timeRemaining < 0 && !appData.icsActive) {
15401             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15402             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15403                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15404                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15405             }
15406         }
15407         DisplayWhiteClock(whiteTimeRemaining - fudge,
15408                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15409     } else {
15410         if(blackNPS >= 0) lastTickLength = 0;
15411         timeRemaining = blackTimeRemaining -= lastTickLength;
15412         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15413             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15414             if(suddenDeath) {
15415                 blackStartMove = forwardMostMove;
15416                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15417             }
15418         }
15419         DisplayBlackClock(blackTimeRemaining - fudge,
15420                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15421     }
15422     if (CheckFlags()) return;
15423
15424     tickStartTM = now;
15425     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15426     StartClockTimer(intendedTickLength);
15427
15428     /* if the time remaining has fallen below the alarm threshold, sound the
15429      * alarm. if the alarm has sounded and (due to a takeback or time control
15430      * with increment) the time remaining has increased to a level above the
15431      * threshold, reset the alarm so it can sound again.
15432      */
15433
15434     if (appData.icsActive && appData.icsAlarm) {
15435
15436         /* make sure we are dealing with the user's clock */
15437         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15438                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15439            )) return;
15440
15441         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15442             alarmSounded = FALSE;
15443         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15444             PlayAlarmSound();
15445             alarmSounded = TRUE;
15446         }
15447     }
15448 }
15449
15450
15451 /* A player has just moved, so stop the previously running
15452    clock and (if in clock mode) start the other one.
15453    We redisplay both clocks in case we're in ICS mode, because
15454    ICS gives us an update to both clocks after every move.
15455    Note that this routine is called *after* forwardMostMove
15456    is updated, so the last fractional tick must be subtracted
15457    from the color that is *not* on move now.
15458 */
15459 void
15460 SwitchClocks(int newMoveNr)
15461 {
15462     long lastTickLength;
15463     TimeMark now;
15464     int flagged = FALSE;
15465
15466     GetTimeMark(&now);
15467
15468     if (StopClockTimer() && appData.clockMode) {
15469         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15470         if (!WhiteOnMove(forwardMostMove)) {
15471             if(blackNPS >= 0) lastTickLength = 0;
15472             blackTimeRemaining -= lastTickLength;
15473            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15474 //         if(pvInfoList[forwardMostMove].time == -1)
15475                  pvInfoList[forwardMostMove].time =               // use GUI time
15476                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15477         } else {
15478            if(whiteNPS >= 0) lastTickLength = 0;
15479            whiteTimeRemaining -= lastTickLength;
15480            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15481 //         if(pvInfoList[forwardMostMove].time == -1)
15482                  pvInfoList[forwardMostMove].time =
15483                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15484         }
15485         flagged = CheckFlags();
15486     }
15487     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15488     CheckTimeControl();
15489
15490     if (flagged || !appData.clockMode) return;
15491
15492     switch (gameMode) {
15493       case MachinePlaysBlack:
15494       case MachinePlaysWhite:
15495       case BeginningOfGame:
15496         if (pausing) return;
15497         break;
15498
15499       case EditGame:
15500       case PlayFromGameFile:
15501       case IcsExamining:
15502         return;
15503
15504       default:
15505         break;
15506     }
15507
15508     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15509         if(WhiteOnMove(forwardMostMove))
15510              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15511         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15512     }
15513
15514     tickStartTM = now;
15515     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15516       whiteTimeRemaining : blackTimeRemaining);
15517     StartClockTimer(intendedTickLength);
15518 }
15519
15520
15521 /* Stop both clocks */
15522 void
15523 StopClocks()
15524 {
15525     long lastTickLength;
15526     TimeMark now;
15527
15528     if (!StopClockTimer()) return;
15529     if (!appData.clockMode) return;
15530
15531     GetTimeMark(&now);
15532
15533     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15534     if (WhiteOnMove(forwardMostMove)) {
15535         if(whiteNPS >= 0) lastTickLength = 0;
15536         whiteTimeRemaining -= lastTickLength;
15537         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15538     } else {
15539         if(blackNPS >= 0) lastTickLength = 0;
15540         blackTimeRemaining -= lastTickLength;
15541         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15542     }
15543     CheckFlags();
15544 }
15545
15546 /* Start clock of player on move.  Time may have been reset, so
15547    if clock is already running, stop and restart it. */
15548 void
15549 StartClocks()
15550 {
15551     (void) StopClockTimer(); /* in case it was running already */
15552     DisplayBothClocks();
15553     if (CheckFlags()) return;
15554
15555     if (!appData.clockMode) return;
15556     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15557
15558     GetTimeMark(&tickStartTM);
15559     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15560       whiteTimeRemaining : blackTimeRemaining);
15561
15562    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15563     whiteNPS = blackNPS = -1;
15564     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15565        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15566         whiteNPS = first.nps;
15567     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15568        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15569         blackNPS = first.nps;
15570     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15571         whiteNPS = second.nps;
15572     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15573         blackNPS = second.nps;
15574     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15575
15576     StartClockTimer(intendedTickLength);
15577 }
15578
15579 char *
15580 TimeString(ms)
15581      long ms;
15582 {
15583     long second, minute, hour, day;
15584     char *sign = "";
15585     static char buf[32];
15586
15587     if (ms > 0 && ms <= 9900) {
15588       /* convert milliseconds to tenths, rounding up */
15589       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15590
15591       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15592       return buf;
15593     }
15594
15595     /* convert milliseconds to seconds, rounding up */
15596     /* use floating point to avoid strangeness of integer division
15597        with negative dividends on many machines */
15598     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15599
15600     if (second < 0) {
15601         sign = "-";
15602         second = -second;
15603     }
15604
15605     day = second / (60 * 60 * 24);
15606     second = second % (60 * 60 * 24);
15607     hour = second / (60 * 60);
15608     second = second % (60 * 60);
15609     minute = second / 60;
15610     second = second % 60;
15611
15612     if (day > 0)
15613       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15614               sign, day, hour, minute, second);
15615     else if (hour > 0)
15616       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15617     else
15618       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15619
15620     return buf;
15621 }
15622
15623
15624 /*
15625  * This is necessary because some C libraries aren't ANSI C compliant yet.
15626  */
15627 char *
15628 StrStr(string, match)
15629      char *string, *match;
15630 {
15631     int i, length;
15632
15633     length = strlen(match);
15634
15635     for (i = strlen(string) - length; i >= 0; i--, string++)
15636       if (!strncmp(match, string, length))
15637         return string;
15638
15639     return NULL;
15640 }
15641
15642 char *
15643 StrCaseStr(string, match)
15644      char *string, *match;
15645 {
15646     int i, j, length;
15647
15648     length = strlen(match);
15649
15650     for (i = strlen(string) - length; i >= 0; i--, string++) {
15651         for (j = 0; j < length; j++) {
15652             if (ToLower(match[j]) != ToLower(string[j]))
15653               break;
15654         }
15655         if (j == length) return string;
15656     }
15657
15658     return NULL;
15659 }
15660
15661 #ifndef _amigados
15662 int
15663 StrCaseCmp(s1, s2)
15664      char *s1, *s2;
15665 {
15666     char c1, c2;
15667
15668     for (;;) {
15669         c1 = ToLower(*s1++);
15670         c2 = ToLower(*s2++);
15671         if (c1 > c2) return 1;
15672         if (c1 < c2) return -1;
15673         if (c1 == NULLCHAR) return 0;
15674     }
15675 }
15676
15677
15678 int
15679 ToLower(c)
15680      int c;
15681 {
15682     return isupper(c) ? tolower(c) : c;
15683 }
15684
15685
15686 int
15687 ToUpper(c)
15688      int c;
15689 {
15690     return islower(c) ? toupper(c) : c;
15691 }
15692 #endif /* !_amigados    */
15693
15694 char *
15695 StrSave(s)
15696      char *s;
15697 {
15698   char *ret;
15699
15700   if ((ret = (char *) malloc(strlen(s) + 1)))
15701     {
15702       safeStrCpy(ret, s, strlen(s)+1);
15703     }
15704   return ret;
15705 }
15706
15707 char *
15708 StrSavePtr(s, savePtr)
15709      char *s, **savePtr;
15710 {
15711     if (*savePtr) {
15712         free(*savePtr);
15713     }
15714     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15715       safeStrCpy(*savePtr, s, strlen(s)+1);
15716     }
15717     return(*savePtr);
15718 }
15719
15720 char *
15721 PGNDate()
15722 {
15723     time_t clock;
15724     struct tm *tm;
15725     char buf[MSG_SIZ];
15726
15727     clock = time((time_t *)NULL);
15728     tm = localtime(&clock);
15729     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15730             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15731     return StrSave(buf);
15732 }
15733
15734
15735 char *
15736 PositionToFEN(move, overrideCastling)
15737      int move;
15738      char *overrideCastling;
15739 {
15740     int i, j, fromX, fromY, toX, toY;
15741     int whiteToPlay;
15742     char buf[128];
15743     char *p, *q;
15744     int emptycount;
15745     ChessSquare piece;
15746
15747     whiteToPlay = (gameMode == EditPosition) ?
15748       !blackPlaysFirst : (move % 2 == 0);
15749     p = buf;
15750
15751     /* Piece placement data */
15752     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15753         emptycount = 0;
15754         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15755             if (boards[move][i][j] == EmptySquare) {
15756                 emptycount++;
15757             } else { ChessSquare piece = boards[move][i][j];
15758                 if (emptycount > 0) {
15759                     if(emptycount<10) /* [HGM] can be >= 10 */
15760                         *p++ = '0' + emptycount;
15761                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15762                     emptycount = 0;
15763                 }
15764                 if(PieceToChar(piece) == '+') {
15765                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15766                     *p++ = '+';
15767                     piece = (ChessSquare)(DEMOTED piece);
15768                 }
15769                 *p++ = PieceToChar(piece);
15770                 if(p[-1] == '~') {
15771                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15772                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15773                     *p++ = '~';
15774                 }
15775             }
15776         }
15777         if (emptycount > 0) {
15778             if(emptycount<10) /* [HGM] can be >= 10 */
15779                 *p++ = '0' + emptycount;
15780             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15781             emptycount = 0;
15782         }
15783         *p++ = '/';
15784     }
15785     *(p - 1) = ' ';
15786
15787     /* [HGM] print Crazyhouse or Shogi holdings */
15788     if( gameInfo.holdingsWidth ) {
15789         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15790         q = p;
15791         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15792             piece = boards[move][i][BOARD_WIDTH-1];
15793             if( piece != EmptySquare )
15794               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15795                   *p++ = PieceToChar(piece);
15796         }
15797         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15798             piece = boards[move][BOARD_HEIGHT-i-1][0];
15799             if( piece != EmptySquare )
15800               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15801                   *p++ = PieceToChar(piece);
15802         }
15803
15804         if( q == p ) *p++ = '-';
15805         *p++ = ']';
15806         *p++ = ' ';
15807     }
15808
15809     /* Active color */
15810     *p++ = whiteToPlay ? 'w' : 'b';
15811     *p++ = ' ';
15812
15813   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15814     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15815   } else {
15816   if(nrCastlingRights) {
15817      q = p;
15818      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15819        /* [HGM] write directly from rights */
15820            if(boards[move][CASTLING][2] != NoRights &&
15821               boards[move][CASTLING][0] != NoRights   )
15822                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15823            if(boards[move][CASTLING][2] != NoRights &&
15824               boards[move][CASTLING][1] != NoRights   )
15825                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15826            if(boards[move][CASTLING][5] != NoRights &&
15827               boards[move][CASTLING][3] != NoRights   )
15828                 *p++ = boards[move][CASTLING][3] + AAA;
15829            if(boards[move][CASTLING][5] != NoRights &&
15830               boards[move][CASTLING][4] != NoRights   )
15831                 *p++ = boards[move][CASTLING][4] + AAA;
15832      } else {
15833
15834         /* [HGM] write true castling rights */
15835         if( nrCastlingRights == 6 ) {
15836             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15837                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15838             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15839                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15840             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15841                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15842             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15843                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15844         }
15845      }
15846      if (q == p) *p++ = '-'; /* No castling rights */
15847      *p++ = ' ';
15848   }
15849
15850   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15851      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15852     /* En passant target square */
15853     if (move > backwardMostMove) {
15854         fromX = moveList[move - 1][0] - AAA;
15855         fromY = moveList[move - 1][1] - ONE;
15856         toX = moveList[move - 1][2] - AAA;
15857         toY = moveList[move - 1][3] - ONE;
15858         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15859             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15860             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15861             fromX == toX) {
15862             /* 2-square pawn move just happened */
15863             *p++ = toX + AAA;
15864             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15865         } else {
15866             *p++ = '-';
15867         }
15868     } else if(move == backwardMostMove) {
15869         // [HGM] perhaps we should always do it like this, and forget the above?
15870         if((signed char)boards[move][EP_STATUS] >= 0) {
15871             *p++ = boards[move][EP_STATUS] + AAA;
15872             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15873         } else {
15874             *p++ = '-';
15875         }
15876     } else {
15877         *p++ = '-';
15878     }
15879     *p++ = ' ';
15880   }
15881   }
15882
15883     /* [HGM] find reversible plies */
15884     {   int i = 0, j=move;
15885
15886         if (appData.debugMode) { int k;
15887             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15888             for(k=backwardMostMove; k<=forwardMostMove; k++)
15889                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15890
15891         }
15892
15893         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15894         if( j == backwardMostMove ) i += initialRulePlies;
15895         sprintf(p, "%d ", i);
15896         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15897     }
15898     /* Fullmove number */
15899     sprintf(p, "%d", (move / 2) + 1);
15900
15901     return StrSave(buf);
15902 }
15903
15904 Boolean
15905 ParseFEN(board, blackPlaysFirst, fen)
15906     Board board;
15907      int *blackPlaysFirst;
15908      char *fen;
15909 {
15910     int i, j;
15911     char *p, c;
15912     int emptycount;
15913     ChessSquare piece;
15914
15915     p = fen;
15916
15917     /* [HGM] by default clear Crazyhouse holdings, if present */
15918     if(gameInfo.holdingsWidth) {
15919        for(i=0; i<BOARD_HEIGHT; i++) {
15920            board[i][0]             = EmptySquare; /* black holdings */
15921            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15922            board[i][1]             = (ChessSquare) 0; /* black counts */
15923            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15924        }
15925     }
15926
15927     /* Piece placement data */
15928     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15929         j = 0;
15930         for (;;) {
15931             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15932                 if (*p == '/') p++;
15933                 emptycount = gameInfo.boardWidth - j;
15934                 while (emptycount--)
15935                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15936                 break;
15937 #if(BOARD_FILES >= 10)
15938             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15939                 p++; emptycount=10;
15940                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15941                 while (emptycount--)
15942                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15943 #endif
15944             } else if (isdigit(*p)) {
15945                 emptycount = *p++ - '0';
15946                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15947                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15948                 while (emptycount--)
15949                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15950             } else if (*p == '+' || isalpha(*p)) {
15951                 if (j >= gameInfo.boardWidth) return FALSE;
15952                 if(*p=='+') {
15953                     piece = CharToPiece(*++p);
15954                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15955                     piece = (ChessSquare) (PROMOTED piece ); p++;
15956                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15957                 } else piece = CharToPiece(*p++);
15958
15959                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15960                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15961                     piece = (ChessSquare) (PROMOTED piece);
15962                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15963                     p++;
15964                 }
15965                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15966             } else {
15967                 return FALSE;
15968             }
15969         }
15970     }
15971     while (*p == '/' || *p == ' ') p++;
15972
15973     /* [HGM] look for Crazyhouse holdings here */
15974     while(*p==' ') p++;
15975     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15976         if(*p == '[') p++;
15977         if(*p == '-' ) p++; /* empty holdings */ else {
15978             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15979             /* if we would allow FEN reading to set board size, we would   */
15980             /* have to add holdings and shift the board read so far here   */
15981             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15982                 p++;
15983                 if((int) piece >= (int) BlackPawn ) {
15984                     i = (int)piece - (int)BlackPawn;
15985                     i = PieceToNumber((ChessSquare)i);
15986                     if( i >= gameInfo.holdingsSize ) return FALSE;
15987                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15988                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15989                 } else {
15990                     i = (int)piece - (int)WhitePawn;
15991                     i = PieceToNumber((ChessSquare)i);
15992                     if( i >= gameInfo.holdingsSize ) return FALSE;
15993                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15994                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15995                 }
15996             }
15997         }
15998         if(*p == ']') p++;
15999     }
16000
16001     while(*p == ' ') p++;
16002
16003     /* Active color */
16004     c = *p++;
16005     if(appData.colorNickNames) {
16006       if( c == appData.colorNickNames[0] ) c = 'w'; else
16007       if( c == appData.colorNickNames[1] ) c = 'b';
16008     }
16009     switch (c) {
16010       case 'w':
16011         *blackPlaysFirst = FALSE;
16012         break;
16013       case 'b':
16014         *blackPlaysFirst = TRUE;
16015         break;
16016       default:
16017         return FALSE;
16018     }
16019
16020     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16021     /* return the extra info in global variiables             */
16022
16023     /* set defaults in case FEN is incomplete */
16024     board[EP_STATUS] = EP_UNKNOWN;
16025     for(i=0; i<nrCastlingRights; i++ ) {
16026         board[CASTLING][i] =
16027             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16028     }   /* assume possible unless obviously impossible */
16029     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16030     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16031     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16032                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16033     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16034     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16035     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16036                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16037     FENrulePlies = 0;
16038
16039     while(*p==' ') p++;
16040     if(nrCastlingRights) {
16041       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16042           /* castling indicator present, so default becomes no castlings */
16043           for(i=0; i<nrCastlingRights; i++ ) {
16044                  board[CASTLING][i] = NoRights;
16045           }
16046       }
16047       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16048              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16049              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16050              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16051         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16052
16053         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16054             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16055             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16056         }
16057         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16058             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16059         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16060                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16061         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16062                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16063         switch(c) {
16064           case'K':
16065               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16066               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16067               board[CASTLING][2] = whiteKingFile;
16068               break;
16069           case'Q':
16070               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16071               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16072               board[CASTLING][2] = whiteKingFile;
16073               break;
16074           case'k':
16075               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16076               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16077               board[CASTLING][5] = blackKingFile;
16078               break;
16079           case'q':
16080               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16081               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16082               board[CASTLING][5] = blackKingFile;
16083           case '-':
16084               break;
16085           default: /* FRC castlings */
16086               if(c >= 'a') { /* black rights */
16087                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16088                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16089                   if(i == BOARD_RGHT) break;
16090                   board[CASTLING][5] = i;
16091                   c -= AAA;
16092                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16093                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16094                   if(c > i)
16095                       board[CASTLING][3] = c;
16096                   else
16097                       board[CASTLING][4] = c;
16098               } else { /* white rights */
16099                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16100                     if(board[0][i] == WhiteKing) break;
16101                   if(i == BOARD_RGHT) break;
16102                   board[CASTLING][2] = i;
16103                   c -= AAA - 'a' + 'A';
16104                   if(board[0][c] >= WhiteKing) break;
16105                   if(c > i)
16106                       board[CASTLING][0] = c;
16107                   else
16108                       board[CASTLING][1] = c;
16109               }
16110         }
16111       }
16112       for(i=0; i<nrCastlingRights; i++)
16113         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16114     if (appData.debugMode) {
16115         fprintf(debugFP, "FEN castling rights:");
16116         for(i=0; i<nrCastlingRights; i++)
16117         fprintf(debugFP, " %d", board[CASTLING][i]);
16118         fprintf(debugFP, "\n");
16119     }
16120
16121       while(*p==' ') p++;
16122     }
16123
16124     /* read e.p. field in games that know e.p. capture */
16125     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16126        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16127       if(*p=='-') {
16128         p++; board[EP_STATUS] = EP_NONE;
16129       } else {
16130          char c = *p++ - AAA;
16131
16132          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16133          if(*p >= '0' && *p <='9') p++;
16134          board[EP_STATUS] = c;
16135       }
16136     }
16137
16138
16139     if(sscanf(p, "%d", &i) == 1) {
16140         FENrulePlies = i; /* 50-move ply counter */
16141         /* (The move number is still ignored)    */
16142     }
16143
16144     return TRUE;
16145 }
16146
16147 void
16148 EditPositionPasteFEN(char *fen)
16149 {
16150   if (fen != NULL) {
16151     Board initial_position;
16152
16153     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16154       DisplayError(_("Bad FEN position in clipboard"), 0);
16155       return ;
16156     } else {
16157       int savedBlackPlaysFirst = blackPlaysFirst;
16158       EditPositionEvent();
16159       blackPlaysFirst = savedBlackPlaysFirst;
16160       CopyBoard(boards[0], initial_position);
16161       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16162       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16163       DisplayBothClocks();
16164       DrawPosition(FALSE, boards[currentMove]);
16165     }
16166   }
16167 }
16168
16169 static char cseq[12] = "\\   ";
16170
16171 Boolean set_cont_sequence(char *new_seq)
16172 {
16173     int len;
16174     Boolean ret;
16175
16176     // handle bad attempts to set the sequence
16177         if (!new_seq)
16178                 return 0; // acceptable error - no debug
16179
16180     len = strlen(new_seq);
16181     ret = (len > 0) && (len < sizeof(cseq));
16182     if (ret)
16183       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16184     else if (appData.debugMode)
16185       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16186     return ret;
16187 }
16188
16189 /*
16190     reformat a source message so words don't cross the width boundary.  internal
16191     newlines are not removed.  returns the wrapped size (no null character unless
16192     included in source message).  If dest is NULL, only calculate the size required
16193     for the dest buffer.  lp argument indicats line position upon entry, and it's
16194     passed back upon exit.
16195 */
16196 int wrap(char *dest, char *src, int count, int width, int *lp)
16197 {
16198     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16199
16200     cseq_len = strlen(cseq);
16201     old_line = line = *lp;
16202     ansi = len = clen = 0;
16203
16204     for (i=0; i < count; i++)
16205     {
16206         if (src[i] == '\033')
16207             ansi = 1;
16208
16209         // if we hit the width, back up
16210         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16211         {
16212             // store i & len in case the word is too long
16213             old_i = i, old_len = len;
16214
16215             // find the end of the last word
16216             while (i && src[i] != ' ' && src[i] != '\n')
16217             {
16218                 i--;
16219                 len--;
16220             }
16221
16222             // word too long?  restore i & len before splitting it
16223             if ((old_i-i+clen) >= width)
16224             {
16225                 i = old_i;
16226                 len = old_len;
16227             }
16228
16229             // extra space?
16230             if (i && src[i-1] == ' ')
16231                 len--;
16232
16233             if (src[i] != ' ' && src[i] != '\n')
16234             {
16235                 i--;
16236                 if (len)
16237                     len--;
16238             }
16239
16240             // now append the newline and continuation sequence
16241             if (dest)
16242                 dest[len] = '\n';
16243             len++;
16244             if (dest)
16245                 strncpy(dest+len, cseq, cseq_len);
16246             len += cseq_len;
16247             line = cseq_len;
16248             clen = cseq_len;
16249             continue;
16250         }
16251
16252         if (dest)
16253             dest[len] = src[i];
16254         len++;
16255         if (!ansi)
16256             line++;
16257         if (src[i] == '\n')
16258             line = 0;
16259         if (src[i] == 'm')
16260             ansi = 0;
16261     }
16262     if (dest && appData.debugMode)
16263     {
16264         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16265             count, width, line, len, *lp);
16266         show_bytes(debugFP, src, count);
16267         fprintf(debugFP, "\ndest: ");
16268         show_bytes(debugFP, dest, len);
16269         fprintf(debugFP, "\n");
16270     }
16271     *lp = dest ? line : old_line;
16272
16273     return len;
16274 }
16275
16276 // [HGM] vari: routines for shelving variations
16277
16278 void
16279 PushInner(int firstMove, int lastMove)
16280 {
16281         int i, j, nrMoves = lastMove - firstMove;
16282
16283         // push current tail of game on stack
16284         savedResult[storedGames] = gameInfo.result;
16285         savedDetails[storedGames] = gameInfo.resultDetails;
16286         gameInfo.resultDetails = NULL;
16287         savedFirst[storedGames] = firstMove;
16288         savedLast [storedGames] = lastMove;
16289         savedFramePtr[storedGames] = framePtr;
16290         framePtr -= nrMoves; // reserve space for the boards
16291         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16292             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16293             for(j=0; j<MOVE_LEN; j++)
16294                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16295             for(j=0; j<2*MOVE_LEN; j++)
16296                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16297             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16298             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16299             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16300             pvInfoList[firstMove+i-1].depth = 0;
16301             commentList[framePtr+i] = commentList[firstMove+i];
16302             commentList[firstMove+i] = NULL;
16303         }
16304
16305         storedGames++;
16306         forwardMostMove = firstMove; // truncate game so we can start variation
16307 }
16308
16309 void
16310 PushTail(int firstMove, int lastMove)
16311 {
16312         if(appData.icsActive) { // only in local mode
16313                 forwardMostMove = currentMove; // mimic old ICS behavior
16314                 return;
16315         }
16316         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16317
16318         PushInner(firstMove, lastMove);
16319         if(storedGames == 1) GreyRevert(FALSE);
16320 }
16321
16322 void
16323 PopInner(Boolean annotate)
16324 {
16325         int i, j, nrMoves;
16326         char buf[8000], moveBuf[20];
16327
16328         storedGames--;
16329         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16330         nrMoves = savedLast[storedGames] - currentMove;
16331         if(annotate) {
16332                 int cnt = 10;
16333                 if(!WhiteOnMove(currentMove))
16334                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16335                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16336                 for(i=currentMove; i<forwardMostMove; i++) {
16337                         if(WhiteOnMove(i))
16338                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16339                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16340                         strcat(buf, moveBuf);
16341                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16342                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16343                 }
16344                 strcat(buf, ")");
16345         }
16346         for(i=1; i<=nrMoves; i++) { // copy last variation back
16347             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16348             for(j=0; j<MOVE_LEN; j++)
16349                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16350             for(j=0; j<2*MOVE_LEN; j++)
16351                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16352             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16353             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16354             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16355             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16356             commentList[currentMove+i] = commentList[framePtr+i];
16357             commentList[framePtr+i] = NULL;
16358         }
16359         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16360         framePtr = savedFramePtr[storedGames];
16361         gameInfo.result = savedResult[storedGames];
16362         if(gameInfo.resultDetails != NULL) {
16363             free(gameInfo.resultDetails);
16364       }
16365         gameInfo.resultDetails = savedDetails[storedGames];
16366         forwardMostMove = currentMove + nrMoves;
16367 }
16368
16369 Boolean
16370 PopTail(Boolean annotate)
16371 {
16372         if(appData.icsActive) return FALSE; // only in local mode
16373         if(!storedGames) return FALSE; // sanity
16374         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16375
16376         PopInner(annotate);
16377
16378         if(storedGames == 0) GreyRevert(TRUE);
16379         return TRUE;
16380 }
16381
16382 void
16383 CleanupTail()
16384 {       // remove all shelved variations
16385         int i;
16386         for(i=0; i<storedGames; i++) {
16387             if(savedDetails[i])
16388                 free(savedDetails[i]);
16389             savedDetails[i] = NULL;
16390         }
16391         for(i=framePtr; i<MAX_MOVES; i++) {
16392                 if(commentList[i]) free(commentList[i]);
16393                 commentList[i] = NULL;
16394         }
16395         framePtr = MAX_MOVES-1;
16396         storedGames = 0;
16397 }
16398
16399 void
16400 LoadVariation(int index, char *text)
16401 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16402         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16403         int level = 0, move;
16404
16405         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16406         // first find outermost bracketing variation
16407         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16408             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16409                 if(*p == '{') wait = '}'; else
16410                 if(*p == '[') wait = ']'; else
16411                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16412                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16413             }
16414             if(*p == wait) wait = NULLCHAR; // closing ]} found
16415             p++;
16416         }
16417         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16418         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16419         end[1] = NULLCHAR; // clip off comment beyond variation
16420         ToNrEvent(currentMove-1);
16421         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16422         // kludge: use ParsePV() to append variation to game
16423         move = currentMove;
16424         ParsePV(start, TRUE, TRUE);
16425         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16426         ClearPremoveHighlights();
16427         CommentPopDown();
16428         ToNrEvent(currentMove+1);
16429 }
16430