Fix display of last move of last match game
[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) syncInterval = nPlayers/2; else
9783     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9784
9785     if(syncInterval) {
9786         p = q = appData.results;
9787         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9788         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9789             DisplayMessage(_("Waiting for other game(s)"),"");
9790             waitingForGame = TRUE;
9791             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9792             return 0;
9793         }
9794         waitingForGame = FALSE;
9795     }
9796
9797     if(appData.tourneyType < 0) {
9798         if(nr>=0 && !pairingReceived) {
9799             char buf[1<<16];
9800             if(pairing.pr == NoProc) {
9801                 if(!appData.pairingEngine[0]) {
9802                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9803                     return 0;
9804                 }
9805                 StartChessProgram(&pairing); // starts the pairing engine
9806             }
9807             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9808             SendToProgram(buf, &pairing);
9809             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9810             SendToProgram(buf, &pairing);
9811             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9812         }
9813         pairingReceived = 0;                              // ... so we continue here 
9814         *swapColors = 0;
9815         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9816         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9817         matchGame = 1; roundNr = nr / syncInterval + 1;
9818     }
9819
9820     if(first.pr != NoProc) return 1; // engines already loaded
9821
9822     // redefine engines, engine dir, etc.
9823     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9824     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9825     SwapEngines(1);
9826     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9827     SwapEngines(1);         // and make that valid for second engine by swapping
9828     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9829     InitEngine(&second, 1);
9830     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9831     return 1;
9832 }
9833
9834 void
9835 NextMatchGame()
9836 {   // performs game initialization that does not invoke engines, and then tries to start the game
9837     int firstWhite, swapColors = 0;
9838     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9839     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9840     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9841     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9842     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9843     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9844     Reset(FALSE, first.pr != NoProc);
9845     appData.noChessProgram = FALSE;
9846     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9847     TwoMachinesEvent();
9848 }
9849
9850 void UserAdjudicationEvent( int result )
9851 {
9852     ChessMove gameResult = GameIsDrawn;
9853
9854     if( result > 0 ) {
9855         gameResult = WhiteWins;
9856     }
9857     else if( result < 0 ) {
9858         gameResult = BlackWins;
9859     }
9860
9861     if( gameMode == TwoMachinesPlay ) {
9862         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9863     }
9864 }
9865
9866
9867 // [HGM] save: calculate checksum of game to make games easily identifiable
9868 int StringCheckSum(char *s)
9869 {
9870         int i = 0;
9871         if(s==NULL) return 0;
9872         while(*s) i = i*259 + *s++;
9873         return i;
9874 }
9875
9876 int GameCheckSum()
9877 {
9878         int i, sum=0;
9879         for(i=backwardMostMove; i<forwardMostMove; i++) {
9880                 sum += pvInfoList[i].depth;
9881                 sum += StringCheckSum(parseList[i]);
9882                 sum += StringCheckSum(commentList[i]);
9883                 sum *= 261;
9884         }
9885         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9886         return sum + StringCheckSum(commentList[i]);
9887 } // end of save patch
9888
9889 void
9890 GameEnds(result, resultDetails, whosays)
9891      ChessMove result;
9892      char *resultDetails;
9893      int whosays;
9894 {
9895     GameMode nextGameMode;
9896     int isIcsGame;
9897     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9898
9899     if(endingGame) return; /* [HGM] crash: forbid recursion */
9900     endingGame = 1;
9901     if(twoBoards) { // [HGM] dual: switch back to one board
9902         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9903         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9904     }
9905     if (appData.debugMode) {
9906       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9907               result, resultDetails ? resultDetails : "(null)", whosays);
9908     }
9909
9910     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9911
9912     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9913         /* If we are playing on ICS, the server decides when the
9914            game is over, but the engine can offer to draw, claim
9915            a draw, or resign.
9916          */
9917 #if ZIPPY
9918         if (appData.zippyPlay && first.initDone) {
9919             if (result == GameIsDrawn) {
9920                 /* In case draw still needs to be claimed */
9921                 SendToICS(ics_prefix);
9922                 SendToICS("draw\n");
9923             } else if (StrCaseStr(resultDetails, "resign")) {
9924                 SendToICS(ics_prefix);
9925                 SendToICS("resign\n");
9926             }
9927         }
9928 #endif
9929         endingGame = 0; /* [HGM] crash */
9930         return;
9931     }
9932
9933     /* If we're loading the game from a file, stop */
9934     if (whosays == GE_FILE) {
9935       (void) StopLoadGameTimer();
9936       gameFileFP = NULL;
9937     }
9938
9939     /* Cancel draw offers */
9940     first.offeredDraw = second.offeredDraw = 0;
9941
9942     /* If this is an ICS game, only ICS can really say it's done;
9943        if not, anyone can. */
9944     isIcsGame = (gameMode == IcsPlayingWhite ||
9945                  gameMode == IcsPlayingBlack ||
9946                  gameMode == IcsObserving    ||
9947                  gameMode == IcsExamining);
9948
9949     if (!isIcsGame || whosays == GE_ICS) {
9950         /* OK -- not an ICS game, or ICS said it was done */
9951         StopClocks();
9952         if (!isIcsGame && !appData.noChessProgram)
9953           SetUserThinkingEnables();
9954
9955         /* [HGM] if a machine claims the game end we verify this claim */
9956         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9957             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9958                 char claimer;
9959                 ChessMove trueResult = (ChessMove) -1;
9960
9961                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9962                                             first.twoMachinesColor[0] :
9963                                             second.twoMachinesColor[0] ;
9964
9965                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9966                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9967                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9968                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9969                 } else
9970                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9971                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9972                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9973                 } else
9974                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9975                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9976                 }
9977
9978                 // now verify win claims, but not in drop games, as we don't understand those yet
9979                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9980                                                  || gameInfo.variant == VariantGreat) &&
9981                     (result == WhiteWins && claimer == 'w' ||
9982                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9983                       if (appData.debugMode) {
9984                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9985                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9986                       }
9987                       if(result != trueResult) {
9988                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9989                               result = claimer == 'w' ? BlackWins : WhiteWins;
9990                               resultDetails = buf;
9991                       }
9992                 } else
9993                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9994                     && (forwardMostMove <= backwardMostMove ||
9995                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9996                         (claimer=='b')==(forwardMostMove&1))
9997                                                                                   ) {
9998                       /* [HGM] verify: draws that were not flagged are false claims */
9999                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10000                       result = claimer == 'w' ? BlackWins : WhiteWins;
10001                       resultDetails = buf;
10002                 }
10003                 /* (Claiming a loss is accepted no questions asked!) */
10004             }
10005             /* [HGM] bare: don't allow bare King to win */
10006             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10007                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10008                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10009                && result != GameIsDrawn)
10010             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10011                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10012                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10013                         if(p >= 0 && p <= (int)WhiteKing) k++;
10014                 }
10015                 if (appData.debugMode) {
10016                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10017                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10018                 }
10019                 if(k <= 1) {
10020                         result = GameIsDrawn;
10021                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10022                         resultDetails = buf;
10023                 }
10024             }
10025         }
10026
10027
10028         if(serverMoves != NULL && !loadFlag) { char c = '=';
10029             if(result==WhiteWins) c = '+';
10030             if(result==BlackWins) c = '-';
10031             if(resultDetails != NULL)
10032                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10033         }
10034         if (resultDetails != NULL) {
10035             gameInfo.result = result;
10036             gameInfo.resultDetails = StrSave(resultDetails);
10037
10038             /* display last move only if game was not loaded from file */
10039             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10040                 DisplayMove(currentMove - 1);
10041
10042             if (forwardMostMove != 0) {
10043                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10044                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10045                                                                 ) {
10046                     if (*appData.saveGameFile != NULLCHAR) {
10047                         SaveGameToFile(appData.saveGameFile, TRUE);
10048                     } else if (appData.autoSaveGames) {
10049                         AutoSaveGame();
10050                     }
10051                     if (*appData.savePositionFile != NULLCHAR) {
10052                         SavePositionToFile(appData.savePositionFile);
10053                     }
10054                 }
10055             }
10056
10057             /* Tell program how game ended in case it is learning */
10058             /* [HGM] Moved this to after saving the PGN, just in case */
10059             /* engine died and we got here through time loss. In that */
10060             /* case we will get a fatal error writing the pipe, which */
10061             /* would otherwise lose us the PGN.                       */
10062             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10063             /* output during GameEnds should never be fatal anymore   */
10064             if (gameMode == MachinePlaysWhite ||
10065                 gameMode == MachinePlaysBlack ||
10066                 gameMode == TwoMachinesPlay ||
10067                 gameMode == IcsPlayingWhite ||
10068                 gameMode == IcsPlayingBlack ||
10069                 gameMode == BeginningOfGame) {
10070                 char buf[MSG_SIZ];
10071                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10072                         resultDetails);
10073                 if (first.pr != NoProc) {
10074                     SendToProgram(buf, &first);
10075                 }
10076                 if (second.pr != NoProc &&
10077                     gameMode == TwoMachinesPlay) {
10078                     SendToProgram(buf, &second);
10079                 }
10080             }
10081         }
10082
10083         if (appData.icsActive) {
10084             if (appData.quietPlay &&
10085                 (gameMode == IcsPlayingWhite ||
10086                  gameMode == IcsPlayingBlack)) {
10087                 SendToICS(ics_prefix);
10088                 SendToICS("set shout 1\n");
10089             }
10090             nextGameMode = IcsIdle;
10091             ics_user_moved = FALSE;
10092             /* clean up premove.  It's ugly when the game has ended and the
10093              * premove highlights are still on the board.
10094              */
10095             if (gotPremove) {
10096               gotPremove = FALSE;
10097               ClearPremoveHighlights();
10098               DrawPosition(FALSE, boards[currentMove]);
10099             }
10100             if (whosays == GE_ICS) {
10101                 switch (result) {
10102                 case WhiteWins:
10103                     if (gameMode == IcsPlayingWhite)
10104                         PlayIcsWinSound();
10105                     else if(gameMode == IcsPlayingBlack)
10106                         PlayIcsLossSound();
10107                     break;
10108                 case BlackWins:
10109                     if (gameMode == IcsPlayingBlack)
10110                         PlayIcsWinSound();
10111                     else if(gameMode == IcsPlayingWhite)
10112                         PlayIcsLossSound();
10113                     break;
10114                 case GameIsDrawn:
10115                     PlayIcsDrawSound();
10116                     break;
10117                 default:
10118                     PlayIcsUnfinishedSound();
10119                 }
10120             }
10121         } else if (gameMode == EditGame ||
10122                    gameMode == PlayFromGameFile ||
10123                    gameMode == AnalyzeMode ||
10124                    gameMode == AnalyzeFile) {
10125             nextGameMode = gameMode;
10126         } else {
10127             nextGameMode = EndOfGame;
10128         }
10129         pausing = FALSE;
10130         ModeHighlight();
10131     } else {
10132         nextGameMode = gameMode;
10133     }
10134
10135     if (appData.noChessProgram) {
10136         gameMode = nextGameMode;
10137         ModeHighlight();
10138         endingGame = 0; /* [HGM] crash */
10139         return;
10140     }
10141
10142     if (first.reuse) {
10143         /* Put first chess program into idle state */
10144         if (first.pr != NoProc &&
10145             (gameMode == MachinePlaysWhite ||
10146              gameMode == MachinePlaysBlack ||
10147              gameMode == TwoMachinesPlay ||
10148              gameMode == IcsPlayingWhite ||
10149              gameMode == IcsPlayingBlack ||
10150              gameMode == BeginningOfGame)) {
10151             SendToProgram("force\n", &first);
10152             if (first.usePing) {
10153               char buf[MSG_SIZ];
10154               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10155               SendToProgram(buf, &first);
10156             }
10157         }
10158     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10159         /* Kill off first chess program */
10160         if (first.isr != NULL)
10161           RemoveInputSource(first.isr);
10162         first.isr = NULL;
10163
10164         if (first.pr != NoProc) {
10165             ExitAnalyzeMode();
10166             DoSleep( appData.delayBeforeQuit );
10167             SendToProgram("quit\n", &first);
10168             DoSleep( appData.delayAfterQuit );
10169             DestroyChildProcess(first.pr, first.useSigterm);
10170         }
10171         first.pr = NoProc;
10172     }
10173     if (second.reuse) {
10174         /* Put second chess program into idle state */
10175         if (second.pr != NoProc &&
10176             gameMode == TwoMachinesPlay) {
10177             SendToProgram("force\n", &second);
10178             if (second.usePing) {
10179               char buf[MSG_SIZ];
10180               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10181               SendToProgram(buf, &second);
10182             }
10183         }
10184     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10185         /* Kill off second chess program */
10186         if (second.isr != NULL)
10187           RemoveInputSource(second.isr);
10188         second.isr = NULL;
10189
10190         if (second.pr != NoProc) {
10191             DoSleep( appData.delayBeforeQuit );
10192             SendToProgram("quit\n", &second);
10193             DoSleep( appData.delayAfterQuit );
10194             DestroyChildProcess(second.pr, second.useSigterm);
10195         }
10196         second.pr = NoProc;
10197     }
10198
10199     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10200         char resChar = '=';
10201         switch (result) {
10202         case WhiteWins:
10203           resChar = '+';
10204           if (first.twoMachinesColor[0] == 'w') {
10205             first.matchWins++;
10206           } else {
10207             second.matchWins++;
10208           }
10209           break;
10210         case BlackWins:
10211           resChar = '-';
10212           if (first.twoMachinesColor[0] == 'b') {
10213             first.matchWins++;
10214           } else {
10215             second.matchWins++;
10216           }
10217           break;
10218         case GameUnfinished:
10219           resChar = ' ';
10220         default:
10221           break;
10222         }
10223
10224         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10225         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10226             ReserveGame(nextGame, resChar); // sets nextGame
10227             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10228             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10229         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10230
10231         if (nextGame <= appData.matchGames && !abortMatch) {
10232             gameMode = nextGameMode;
10233             matchGame = nextGame; // this will be overruled in tourney mode!
10234             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10235             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10236             endingGame = 0; /* [HGM] crash */
10237             return;
10238         } else {
10239             gameMode = nextGameMode;
10240             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10241                      first.tidy, second.tidy,
10242                      first.matchWins, second.matchWins,
10243                      appData.matchGames - (first.matchWins + second.matchWins));
10244             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10245             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10246                 first.twoMachinesColor = "black\n";
10247                 second.twoMachinesColor = "white\n";
10248             } else {
10249                 first.twoMachinesColor = "white\n";
10250                 second.twoMachinesColor = "black\n";
10251             }
10252         }
10253     }
10254     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10255         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10256       ExitAnalyzeMode();
10257     gameMode = nextGameMode;
10258     ModeHighlight();
10259     endingGame = 0;  /* [HGM] crash */
10260     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10261         if(matchMode == TRUE) { // match through command line: exit with or without popup
10262             if(ranking) {
10263                 ToNrEvent(forwardMostMove);
10264                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10265                 else ExitEvent(0);
10266             } else DisplayFatalError(buf, 0, 0);
10267         } else { // match through menu; just stop, with or without popup
10268             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10269             if(ranking){
10270                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10271             } else DisplayNote(buf);
10272       }
10273       if(ranking) free(ranking);
10274     }
10275 }
10276
10277 /* Assumes program was just initialized (initString sent).
10278    Leaves program in force mode. */
10279 void
10280 FeedMovesToProgram(cps, upto)
10281      ChessProgramState *cps;
10282      int upto;
10283 {
10284     int i;
10285
10286     if (appData.debugMode)
10287       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10288               startedFromSetupPosition ? "position and " : "",
10289               backwardMostMove, upto, cps->which);
10290     if(currentlyInitializedVariant != gameInfo.variant) {
10291       char buf[MSG_SIZ];
10292         // [HGM] variantswitch: make engine aware of new variant
10293         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10294                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10295         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10296         SendToProgram(buf, cps);
10297         currentlyInitializedVariant = gameInfo.variant;
10298     }
10299     SendToProgram("force\n", cps);
10300     if (startedFromSetupPosition) {
10301         SendBoard(cps, backwardMostMove);
10302     if (appData.debugMode) {
10303         fprintf(debugFP, "feedMoves\n");
10304     }
10305     }
10306     for (i = backwardMostMove; i < upto; i++) {
10307         SendMoveToProgram(i, cps);
10308     }
10309 }
10310
10311
10312 int
10313 ResurrectChessProgram()
10314 {
10315      /* The chess program may have exited.
10316         If so, restart it and feed it all the moves made so far. */
10317     static int doInit = 0;
10318
10319     if (appData.noChessProgram) return 1;
10320
10321     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10322         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10323         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10324         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10325     } else {
10326         if (first.pr != NoProc) return 1;
10327         StartChessProgram(&first);
10328     }
10329     InitChessProgram(&first, FALSE);
10330     FeedMovesToProgram(&first, currentMove);
10331
10332     if (!first.sendTime) {
10333         /* can't tell gnuchess what its clock should read,
10334            so we bow to its notion. */
10335         ResetClocks();
10336         timeRemaining[0][currentMove] = whiteTimeRemaining;
10337         timeRemaining[1][currentMove] = blackTimeRemaining;
10338     }
10339
10340     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10341                 appData.icsEngineAnalyze) && first.analysisSupport) {
10342       SendToProgram("analyze\n", &first);
10343       first.analyzing = TRUE;
10344     }
10345     return 1;
10346 }
10347
10348 /*
10349  * Button procedures
10350  */
10351 void
10352 Reset(redraw, init)
10353      int redraw, init;
10354 {
10355     int i;
10356
10357     if (appData.debugMode) {
10358         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10359                 redraw, init, gameMode);
10360     }
10361     CleanupTail(); // [HGM] vari: delete any stored variations
10362     pausing = pauseExamInvalid = FALSE;
10363     startedFromSetupPosition = blackPlaysFirst = FALSE;
10364     firstMove = TRUE;
10365     whiteFlag = blackFlag = FALSE;
10366     userOfferedDraw = FALSE;
10367     hintRequested = bookRequested = FALSE;
10368     first.maybeThinking = FALSE;
10369     second.maybeThinking = FALSE;
10370     first.bookSuspend = FALSE; // [HGM] book
10371     second.bookSuspend = FALSE;
10372     thinkOutput[0] = NULLCHAR;
10373     lastHint[0] = NULLCHAR;
10374     ClearGameInfo(&gameInfo);
10375     gameInfo.variant = StringToVariant(appData.variant);
10376     ics_user_moved = ics_clock_paused = FALSE;
10377     ics_getting_history = H_FALSE;
10378     ics_gamenum = -1;
10379     white_holding[0] = black_holding[0] = NULLCHAR;
10380     ClearProgramStats();
10381     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10382
10383     ResetFrontEnd();
10384     ClearHighlights();
10385     flipView = appData.flipView;
10386     ClearPremoveHighlights();
10387     gotPremove = FALSE;
10388     alarmSounded = FALSE;
10389
10390     GameEnds(EndOfFile, NULL, GE_PLAYER);
10391     if(appData.serverMovesName != NULL) {
10392         /* [HGM] prepare to make moves file for broadcasting */
10393         clock_t t = clock();
10394         if(serverMoves != NULL) fclose(serverMoves);
10395         serverMoves = fopen(appData.serverMovesName, "r");
10396         if(serverMoves != NULL) {
10397             fclose(serverMoves);
10398             /* delay 15 sec before overwriting, so all clients can see end */
10399             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10400         }
10401         serverMoves = fopen(appData.serverMovesName, "w");
10402     }
10403
10404     ExitAnalyzeMode();
10405     gameMode = BeginningOfGame;
10406     ModeHighlight();
10407     if(appData.icsActive) gameInfo.variant = VariantNormal;
10408     currentMove = forwardMostMove = backwardMostMove = 0;
10409     InitPosition(redraw);
10410     for (i = 0; i < MAX_MOVES; i++) {
10411         if (commentList[i] != NULL) {
10412             free(commentList[i]);
10413             commentList[i] = NULL;
10414         }
10415     }
10416     ResetClocks();
10417     timeRemaining[0][0] = whiteTimeRemaining;
10418     timeRemaining[1][0] = blackTimeRemaining;
10419
10420     if (first.pr == NULL) {
10421         StartChessProgram(&first);
10422     }
10423     if (init) {
10424             InitChessProgram(&first, startedFromSetupPosition);
10425     }
10426     DisplayTitle("");
10427     DisplayMessage("", "");
10428     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10429     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10430 }
10431
10432 void
10433 AutoPlayGameLoop()
10434 {
10435     for (;;) {
10436         if (!AutoPlayOneMove())
10437           return;
10438         if (matchMode || appData.timeDelay == 0)
10439           continue;
10440         if (appData.timeDelay < 0)
10441           return;
10442         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10443         break;
10444     }
10445 }
10446
10447
10448 int
10449 AutoPlayOneMove()
10450 {
10451     int fromX, fromY, toX, toY;
10452
10453     if (appData.debugMode) {
10454       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10455     }
10456
10457     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10458       return FALSE;
10459
10460     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10461       pvInfoList[currentMove].depth = programStats.depth;
10462       pvInfoList[currentMove].score = programStats.score;
10463       pvInfoList[currentMove].time  = 0;
10464       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10465     }
10466
10467     if (currentMove >= forwardMostMove) {
10468       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10469       gameMode = EditGame;
10470       ModeHighlight();
10471
10472       /* [AS] Clear current move marker at the end of a game */
10473       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10474
10475       return FALSE;
10476     }
10477
10478     toX = moveList[currentMove][2] - AAA;
10479     toY = moveList[currentMove][3] - ONE;
10480
10481     if (moveList[currentMove][1] == '@') {
10482         if (appData.highlightLastMove) {
10483             SetHighlights(-1, -1, toX, toY);
10484         }
10485     } else {
10486         fromX = moveList[currentMove][0] - AAA;
10487         fromY = moveList[currentMove][1] - ONE;
10488
10489         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10490
10491         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10492
10493         if (appData.highlightLastMove) {
10494             SetHighlights(fromX, fromY, toX, toY);
10495         }
10496     }
10497     DisplayMove(currentMove);
10498     SendMoveToProgram(currentMove++, &first);
10499     DisplayBothClocks();
10500     DrawPosition(FALSE, boards[currentMove]);
10501     // [HGM] PV info: always display, routine tests if empty
10502     DisplayComment(currentMove - 1, commentList[currentMove]);
10503     return TRUE;
10504 }
10505
10506
10507 int
10508 LoadGameOneMove(readAhead)
10509      ChessMove readAhead;
10510 {
10511     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10512     char promoChar = NULLCHAR;
10513     ChessMove moveType;
10514     char move[MSG_SIZ];
10515     char *p, *q;
10516
10517     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10518         gameMode != AnalyzeMode && gameMode != Training) {
10519         gameFileFP = NULL;
10520         return FALSE;
10521     }
10522
10523     yyboardindex = forwardMostMove;
10524     if (readAhead != EndOfFile) {
10525       moveType = readAhead;
10526     } else {
10527       if (gameFileFP == NULL)
10528           return FALSE;
10529       moveType = (ChessMove) Myylex();
10530     }
10531
10532     done = FALSE;
10533     switch (moveType) {
10534       case Comment:
10535         if (appData.debugMode)
10536           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10537         p = yy_text;
10538
10539         /* append the comment but don't display it */
10540         AppendComment(currentMove, p, FALSE);
10541         return TRUE;
10542
10543       case WhiteCapturesEnPassant:
10544       case BlackCapturesEnPassant:
10545       case WhitePromotion:
10546       case BlackPromotion:
10547       case WhiteNonPromotion:
10548       case BlackNonPromotion:
10549       case NormalMove:
10550       case WhiteKingSideCastle:
10551       case WhiteQueenSideCastle:
10552       case BlackKingSideCastle:
10553       case BlackQueenSideCastle:
10554       case WhiteKingSideCastleWild:
10555       case WhiteQueenSideCastleWild:
10556       case BlackKingSideCastleWild:
10557       case BlackQueenSideCastleWild:
10558       /* PUSH Fabien */
10559       case WhiteHSideCastleFR:
10560       case WhiteASideCastleFR:
10561       case BlackHSideCastleFR:
10562       case BlackASideCastleFR:
10563       /* POP Fabien */
10564         if (appData.debugMode)
10565           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10566         fromX = currentMoveString[0] - AAA;
10567         fromY = currentMoveString[1] - ONE;
10568         toX = currentMoveString[2] - AAA;
10569         toY = currentMoveString[3] - ONE;
10570         promoChar = currentMoveString[4];
10571         break;
10572
10573       case WhiteDrop:
10574       case BlackDrop:
10575         if (appData.debugMode)
10576           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10577         fromX = moveType == WhiteDrop ?
10578           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10579         (int) CharToPiece(ToLower(currentMoveString[0]));
10580         fromY = DROP_RANK;
10581         toX = currentMoveString[2] - AAA;
10582         toY = currentMoveString[3] - ONE;
10583         break;
10584
10585       case WhiteWins:
10586       case BlackWins:
10587       case GameIsDrawn:
10588       case GameUnfinished:
10589         if (appData.debugMode)
10590           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10591         p = strchr(yy_text, '{');
10592         if (p == NULL) p = strchr(yy_text, '(');
10593         if (p == NULL) {
10594             p = yy_text;
10595             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10596         } else {
10597             q = strchr(p, *p == '{' ? '}' : ')');
10598             if (q != NULL) *q = NULLCHAR;
10599             p++;
10600         }
10601         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10602         GameEnds(moveType, p, GE_FILE);
10603         done = TRUE;
10604         if (cmailMsgLoaded) {
10605             ClearHighlights();
10606             flipView = WhiteOnMove(currentMove);
10607             if (moveType == GameUnfinished) flipView = !flipView;
10608             if (appData.debugMode)
10609               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10610         }
10611         break;
10612
10613       case EndOfFile:
10614         if (appData.debugMode)
10615           fprintf(debugFP, "Parser hit end of file\n");
10616         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10617           case MT_NONE:
10618           case MT_CHECK:
10619             break;
10620           case MT_CHECKMATE:
10621           case MT_STAINMATE:
10622             if (WhiteOnMove(currentMove)) {
10623                 GameEnds(BlackWins, "Black mates", GE_FILE);
10624             } else {
10625                 GameEnds(WhiteWins, "White mates", GE_FILE);
10626             }
10627             break;
10628           case MT_STALEMATE:
10629             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10630             break;
10631         }
10632         done = TRUE;
10633         break;
10634
10635       case MoveNumberOne:
10636         if (lastLoadGameStart == GNUChessGame) {
10637             /* GNUChessGames have numbers, but they aren't move numbers */
10638             if (appData.debugMode)
10639               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10640                       yy_text, (int) moveType);
10641             return LoadGameOneMove(EndOfFile); /* tail recursion */
10642         }
10643         /* else fall thru */
10644
10645       case XBoardGame:
10646       case GNUChessGame:
10647       case PGNTag:
10648         /* Reached start of next game in file */
10649         if (appData.debugMode)
10650           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10651         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10652           case MT_NONE:
10653           case MT_CHECK:
10654             break;
10655           case MT_CHECKMATE:
10656           case MT_STAINMATE:
10657             if (WhiteOnMove(currentMove)) {
10658                 GameEnds(BlackWins, "Black mates", GE_FILE);
10659             } else {
10660                 GameEnds(WhiteWins, "White mates", GE_FILE);
10661             }
10662             break;
10663           case MT_STALEMATE:
10664             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10665             break;
10666         }
10667         done = TRUE;
10668         break;
10669
10670       case PositionDiagram:     /* should not happen; ignore */
10671       case ElapsedTime:         /* ignore */
10672       case NAG:                 /* ignore */
10673         if (appData.debugMode)
10674           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10675                   yy_text, (int) moveType);
10676         return LoadGameOneMove(EndOfFile); /* tail recursion */
10677
10678       case IllegalMove:
10679         if (appData.testLegality) {
10680             if (appData.debugMode)
10681               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10682             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10683                     (forwardMostMove / 2) + 1,
10684                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10685             DisplayError(move, 0);
10686             done = TRUE;
10687         } else {
10688             if (appData.debugMode)
10689               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10690                       yy_text, currentMoveString);
10691             fromX = currentMoveString[0] - AAA;
10692             fromY = currentMoveString[1] - ONE;
10693             toX = currentMoveString[2] - AAA;
10694             toY = currentMoveString[3] - ONE;
10695             promoChar = currentMoveString[4];
10696         }
10697         break;
10698
10699       case AmbiguousMove:
10700         if (appData.debugMode)
10701           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10702         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10703                 (forwardMostMove / 2) + 1,
10704                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10705         DisplayError(move, 0);
10706         done = TRUE;
10707         break;
10708
10709       default:
10710       case ImpossibleMove:
10711         if (appData.debugMode)
10712           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10713         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10714                 (forwardMostMove / 2) + 1,
10715                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10716         DisplayError(move, 0);
10717         done = TRUE;
10718         break;
10719     }
10720
10721     if (done) {
10722         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10723             DrawPosition(FALSE, boards[currentMove]);
10724             DisplayBothClocks();
10725             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10726               DisplayComment(currentMove - 1, commentList[currentMove]);
10727         }
10728         (void) StopLoadGameTimer();
10729         gameFileFP = NULL;
10730         cmailOldMove = forwardMostMove;
10731         return FALSE;
10732     } else {
10733         /* currentMoveString is set as a side-effect of yylex */
10734
10735         thinkOutput[0] = NULLCHAR;
10736         MakeMove(fromX, fromY, toX, toY, promoChar);
10737         currentMove = forwardMostMove;
10738         return TRUE;
10739     }
10740 }
10741
10742 /* Load the nth game from the given file */
10743 int
10744 LoadGameFromFile(filename, n, title, useList)
10745      char *filename;
10746      int n;
10747      char *title;
10748      /*Boolean*/ int useList;
10749 {
10750     FILE *f;
10751     char buf[MSG_SIZ];
10752
10753     if (strcmp(filename, "-") == 0) {
10754         f = stdin;
10755         title = "stdin";
10756     } else {
10757         f = fopen(filename, "rb");
10758         if (f == NULL) {
10759           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10760             DisplayError(buf, errno);
10761             return FALSE;
10762         }
10763     }
10764     if (fseek(f, 0, 0) == -1) {
10765         /* f is not seekable; probably a pipe */
10766         useList = FALSE;
10767     }
10768     if (useList && n == 0) {
10769         int error = GameListBuild(f);
10770         if (error) {
10771             DisplayError(_("Cannot build game list"), error);
10772         } else if (!ListEmpty(&gameList) &&
10773                    ((ListGame *) gameList.tailPred)->number > 1) {
10774             GameListPopUp(f, title);
10775             return TRUE;
10776         }
10777         GameListDestroy();
10778         n = 1;
10779     }
10780     if (n == 0) n = 1;
10781     return LoadGame(f, n, title, FALSE);
10782 }
10783
10784
10785 void
10786 MakeRegisteredMove()
10787 {
10788     int fromX, fromY, toX, toY;
10789     char promoChar;
10790     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10791         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10792           case CMAIL_MOVE:
10793           case CMAIL_DRAW:
10794             if (appData.debugMode)
10795               fprintf(debugFP, "Restoring %s for game %d\n",
10796                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10797
10798             thinkOutput[0] = NULLCHAR;
10799             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10800             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10801             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10802             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10803             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10804             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10805             MakeMove(fromX, fromY, toX, toY, promoChar);
10806             ShowMove(fromX, fromY, toX, toY);
10807
10808             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10809               case MT_NONE:
10810               case MT_CHECK:
10811                 break;
10812
10813               case MT_CHECKMATE:
10814               case MT_STAINMATE:
10815                 if (WhiteOnMove(currentMove)) {
10816                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10817                 } else {
10818                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10819                 }
10820                 break;
10821
10822               case MT_STALEMATE:
10823                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10824                 break;
10825             }
10826
10827             break;
10828
10829           case CMAIL_RESIGN:
10830             if (WhiteOnMove(currentMove)) {
10831                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10832             } else {
10833                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10834             }
10835             break;
10836
10837           case CMAIL_ACCEPT:
10838             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10839             break;
10840
10841           default:
10842             break;
10843         }
10844     }
10845
10846     return;
10847 }
10848
10849 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10850 int
10851 CmailLoadGame(f, gameNumber, title, useList)
10852      FILE *f;
10853      int gameNumber;
10854      char *title;
10855      int useList;
10856 {
10857     int retVal;
10858
10859     if (gameNumber > nCmailGames) {
10860         DisplayError(_("No more games in this message"), 0);
10861         return FALSE;
10862     }
10863     if (f == lastLoadGameFP) {
10864         int offset = gameNumber - lastLoadGameNumber;
10865         if (offset == 0) {
10866             cmailMsg[0] = NULLCHAR;
10867             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10868                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10869                 nCmailMovesRegistered--;
10870             }
10871             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10872             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10873                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10874             }
10875         } else {
10876             if (! RegisterMove()) return FALSE;
10877         }
10878     }
10879
10880     retVal = LoadGame(f, gameNumber, title, useList);
10881
10882     /* Make move registered during previous look at this game, if any */
10883     MakeRegisteredMove();
10884
10885     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10886         commentList[currentMove]
10887           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10888         DisplayComment(currentMove - 1, commentList[currentMove]);
10889     }
10890
10891     return retVal;
10892 }
10893
10894 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10895 int
10896 ReloadGame(offset)
10897      int offset;
10898 {
10899     int gameNumber = lastLoadGameNumber + offset;
10900     if (lastLoadGameFP == NULL) {
10901         DisplayError(_("No game has been loaded yet"), 0);
10902         return FALSE;
10903     }
10904     if (gameNumber <= 0) {
10905         DisplayError(_("Can't back up any further"), 0);
10906         return FALSE;
10907     }
10908     if (cmailMsgLoaded) {
10909         return CmailLoadGame(lastLoadGameFP, gameNumber,
10910                              lastLoadGameTitle, lastLoadGameUseList);
10911     } else {
10912         return LoadGame(lastLoadGameFP, gameNumber,
10913                         lastLoadGameTitle, lastLoadGameUseList);
10914     }
10915 }
10916
10917
10918
10919 /* Load the nth game from open file f */
10920 int
10921 LoadGame(f, gameNumber, title, useList)
10922      FILE *f;
10923      int gameNumber;
10924      char *title;
10925      int useList;
10926 {
10927     ChessMove cm;
10928     char buf[MSG_SIZ];
10929     int gn = gameNumber;
10930     ListGame *lg = NULL;
10931     int numPGNTags = 0;
10932     int err;
10933     GameMode oldGameMode;
10934     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10935
10936     if (appData.debugMode)
10937         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10938
10939     if (gameMode == Training )
10940         SetTrainingModeOff();
10941
10942     oldGameMode = gameMode;
10943     if (gameMode != BeginningOfGame) {
10944       Reset(FALSE, TRUE);
10945     }
10946
10947     gameFileFP = f;
10948     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10949         fclose(lastLoadGameFP);
10950     }
10951
10952     if (useList) {
10953         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10954
10955         if (lg) {
10956             fseek(f, lg->offset, 0);
10957             GameListHighlight(gameNumber);
10958             gn = 1;
10959         }
10960         else {
10961             DisplayError(_("Game number out of range"), 0);
10962             return FALSE;
10963         }
10964     } else {
10965         GameListDestroy();
10966         if (fseek(f, 0, 0) == -1) {
10967             if (f == lastLoadGameFP ?
10968                 gameNumber == lastLoadGameNumber + 1 :
10969                 gameNumber == 1) {
10970                 gn = 1;
10971             } else {
10972                 DisplayError(_("Can't seek on game file"), 0);
10973                 return FALSE;
10974             }
10975         }
10976     }
10977     lastLoadGameFP = f;
10978     lastLoadGameNumber = gameNumber;
10979     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10980     lastLoadGameUseList = useList;
10981
10982     yynewfile(f);
10983
10984     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10985       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10986                 lg->gameInfo.black);
10987             DisplayTitle(buf);
10988     } else if (*title != NULLCHAR) {
10989         if (gameNumber > 1) {
10990           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10991             DisplayTitle(buf);
10992         } else {
10993             DisplayTitle(title);
10994         }
10995     }
10996
10997     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10998         gameMode = PlayFromGameFile;
10999         ModeHighlight();
11000     }
11001
11002     currentMove = forwardMostMove = backwardMostMove = 0;
11003     CopyBoard(boards[0], initialPosition);
11004     StopClocks();
11005
11006     /*
11007      * Skip the first gn-1 games in the file.
11008      * Also skip over anything that precedes an identifiable
11009      * start of game marker, to avoid being confused by
11010      * garbage at the start of the file.  Currently
11011      * recognized start of game markers are the move number "1",
11012      * the pattern "gnuchess .* game", the pattern
11013      * "^[#;%] [^ ]* game file", and a PGN tag block.
11014      * A game that starts with one of the latter two patterns
11015      * will also have a move number 1, possibly
11016      * following a position diagram.
11017      * 5-4-02: Let's try being more lenient and allowing a game to
11018      * start with an unnumbered move.  Does that break anything?
11019      */
11020     cm = lastLoadGameStart = EndOfFile;
11021     while (gn > 0) {
11022         yyboardindex = forwardMostMove;
11023         cm = (ChessMove) Myylex();
11024         switch (cm) {
11025           case EndOfFile:
11026             if (cmailMsgLoaded) {
11027                 nCmailGames = CMAIL_MAX_GAMES - gn;
11028             } else {
11029                 Reset(TRUE, TRUE);
11030                 DisplayError(_("Game not found in file"), 0);
11031             }
11032             return FALSE;
11033
11034           case GNUChessGame:
11035           case XBoardGame:
11036             gn--;
11037             lastLoadGameStart = cm;
11038             break;
11039
11040           case MoveNumberOne:
11041             switch (lastLoadGameStart) {
11042               case GNUChessGame:
11043               case XBoardGame:
11044               case PGNTag:
11045                 break;
11046               case MoveNumberOne:
11047               case EndOfFile:
11048                 gn--;           /* count this game */
11049                 lastLoadGameStart = cm;
11050                 break;
11051               default:
11052                 /* impossible */
11053                 break;
11054             }
11055             break;
11056
11057           case PGNTag:
11058             switch (lastLoadGameStart) {
11059               case GNUChessGame:
11060               case PGNTag:
11061               case MoveNumberOne:
11062               case EndOfFile:
11063                 gn--;           /* count this game */
11064                 lastLoadGameStart = cm;
11065                 break;
11066               case XBoardGame:
11067                 lastLoadGameStart = cm; /* game counted already */
11068                 break;
11069               default:
11070                 /* impossible */
11071                 break;
11072             }
11073             if (gn > 0) {
11074                 do {
11075                     yyboardindex = forwardMostMove;
11076                     cm = (ChessMove) Myylex();
11077                 } while (cm == PGNTag || cm == Comment);
11078             }
11079             break;
11080
11081           case WhiteWins:
11082           case BlackWins:
11083           case GameIsDrawn:
11084             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11085                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11086                     != CMAIL_OLD_RESULT) {
11087                     nCmailResults ++ ;
11088                     cmailResult[  CMAIL_MAX_GAMES
11089                                 - gn - 1] = CMAIL_OLD_RESULT;
11090                 }
11091             }
11092             break;
11093
11094           case NormalMove:
11095             /* Only a NormalMove can be at the start of a game
11096              * without a position diagram. */
11097             if (lastLoadGameStart == EndOfFile ) {
11098               gn--;
11099               lastLoadGameStart = MoveNumberOne;
11100             }
11101             break;
11102
11103           default:
11104             break;
11105         }
11106     }
11107
11108     if (appData.debugMode)
11109       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11110
11111     if (cm == XBoardGame) {
11112         /* Skip any header junk before position diagram and/or move 1 */
11113         for (;;) {
11114             yyboardindex = forwardMostMove;
11115             cm = (ChessMove) Myylex();
11116
11117             if (cm == EndOfFile ||
11118                 cm == GNUChessGame || cm == XBoardGame) {
11119                 /* Empty game; pretend end-of-file and handle later */
11120                 cm = EndOfFile;
11121                 break;
11122             }
11123
11124             if (cm == MoveNumberOne || cm == PositionDiagram ||
11125                 cm == PGNTag || cm == Comment)
11126               break;
11127         }
11128     } else if (cm == GNUChessGame) {
11129         if (gameInfo.event != NULL) {
11130             free(gameInfo.event);
11131         }
11132         gameInfo.event = StrSave(yy_text);
11133     }
11134
11135     startedFromSetupPosition = FALSE;
11136     while (cm == PGNTag) {
11137         if (appData.debugMode)
11138           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11139         err = ParsePGNTag(yy_text, &gameInfo);
11140         if (!err) numPGNTags++;
11141
11142         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11143         if(gameInfo.variant != oldVariant) {
11144             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11145             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11146             InitPosition(TRUE);
11147             oldVariant = gameInfo.variant;
11148             if (appData.debugMode)
11149               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11150         }
11151
11152
11153         if (gameInfo.fen != NULL) {
11154           Board initial_position;
11155           startedFromSetupPosition = TRUE;
11156           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11157             Reset(TRUE, TRUE);
11158             DisplayError(_("Bad FEN position in file"), 0);
11159             return FALSE;
11160           }
11161           CopyBoard(boards[0], initial_position);
11162           if (blackPlaysFirst) {
11163             currentMove = forwardMostMove = backwardMostMove = 1;
11164             CopyBoard(boards[1], initial_position);
11165             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11166             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11167             timeRemaining[0][1] = whiteTimeRemaining;
11168             timeRemaining[1][1] = blackTimeRemaining;
11169             if (commentList[0] != NULL) {
11170               commentList[1] = commentList[0];
11171               commentList[0] = NULL;
11172             }
11173           } else {
11174             currentMove = forwardMostMove = backwardMostMove = 0;
11175           }
11176           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11177           {   int i;
11178               initialRulePlies = FENrulePlies;
11179               for( i=0; i< nrCastlingRights; i++ )
11180                   initialRights[i] = initial_position[CASTLING][i];
11181           }
11182           yyboardindex = forwardMostMove;
11183           free(gameInfo.fen);
11184           gameInfo.fen = NULL;
11185         }
11186
11187         yyboardindex = forwardMostMove;
11188         cm = (ChessMove) Myylex();
11189
11190         /* Handle comments interspersed among the tags */
11191         while (cm == Comment) {
11192             char *p;
11193             if (appData.debugMode)
11194               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11195             p = yy_text;
11196             AppendComment(currentMove, p, FALSE);
11197             yyboardindex = forwardMostMove;
11198             cm = (ChessMove) Myylex();
11199         }
11200     }
11201
11202     /* don't rely on existence of Event tag since if game was
11203      * pasted from clipboard the Event tag may not exist
11204      */
11205     if (numPGNTags > 0){
11206         char *tags;
11207         if (gameInfo.variant == VariantNormal) {
11208           VariantClass v = StringToVariant(gameInfo.event);
11209           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11210           if(v < VariantShogi) gameInfo.variant = v;
11211         }
11212         if (!matchMode) {
11213           if( appData.autoDisplayTags ) {
11214             tags = PGNTags(&gameInfo);
11215             TagsPopUp(tags, CmailMsg());
11216             free(tags);
11217           }
11218         }
11219     } else {
11220         /* Make something up, but don't display it now */
11221         SetGameInfo();
11222         TagsPopDown();
11223     }
11224
11225     if (cm == PositionDiagram) {
11226         int i, j;
11227         char *p;
11228         Board initial_position;
11229
11230         if (appData.debugMode)
11231           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11232
11233         if (!startedFromSetupPosition) {
11234             p = yy_text;
11235             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11236               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11237                 switch (*p) {
11238                   case '{':
11239                   case '[':
11240                   case '-':
11241                   case ' ':
11242                   case '\t':
11243                   case '\n':
11244                   case '\r':
11245                     break;
11246                   default:
11247                     initial_position[i][j++] = CharToPiece(*p);
11248                     break;
11249                 }
11250             while (*p == ' ' || *p == '\t' ||
11251                    *p == '\n' || *p == '\r') p++;
11252
11253             if (strncmp(p, "black", strlen("black"))==0)
11254               blackPlaysFirst = TRUE;
11255             else
11256               blackPlaysFirst = FALSE;
11257             startedFromSetupPosition = TRUE;
11258
11259             CopyBoard(boards[0], initial_position);
11260             if (blackPlaysFirst) {
11261                 currentMove = forwardMostMove = backwardMostMove = 1;
11262                 CopyBoard(boards[1], initial_position);
11263                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11264                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11265                 timeRemaining[0][1] = whiteTimeRemaining;
11266                 timeRemaining[1][1] = blackTimeRemaining;
11267                 if (commentList[0] != NULL) {
11268                     commentList[1] = commentList[0];
11269                     commentList[0] = NULL;
11270                 }
11271             } else {
11272                 currentMove = forwardMostMove = backwardMostMove = 0;
11273             }
11274         }
11275         yyboardindex = forwardMostMove;
11276         cm = (ChessMove) Myylex();
11277     }
11278
11279     if (first.pr == NoProc) {
11280         StartChessProgram(&first);
11281     }
11282     InitChessProgram(&first, FALSE);
11283     SendToProgram("force\n", &first);
11284     if (startedFromSetupPosition) {
11285         SendBoard(&first, forwardMostMove);
11286     if (appData.debugMode) {
11287         fprintf(debugFP, "Load Game\n");
11288     }
11289         DisplayBothClocks();
11290     }
11291
11292     /* [HGM] server: flag to write setup moves in broadcast file as one */
11293     loadFlag = appData.suppressLoadMoves;
11294
11295     while (cm == Comment) {
11296         char *p;
11297         if (appData.debugMode)
11298           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11299         p = yy_text;
11300         AppendComment(currentMove, p, FALSE);
11301         yyboardindex = forwardMostMove;
11302         cm = (ChessMove) Myylex();
11303     }
11304
11305     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11306         cm == WhiteWins || cm == BlackWins ||
11307         cm == GameIsDrawn || cm == GameUnfinished) {
11308         DisplayMessage("", _("No moves in game"));
11309         if (cmailMsgLoaded) {
11310             if (appData.debugMode)
11311               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11312             ClearHighlights();
11313             flipView = FALSE;
11314         }
11315         DrawPosition(FALSE, boards[currentMove]);
11316         DisplayBothClocks();
11317         gameMode = EditGame;
11318         ModeHighlight();
11319         gameFileFP = NULL;
11320         cmailOldMove = 0;
11321         return TRUE;
11322     }
11323
11324     // [HGM] PV info: routine tests if comment empty
11325     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11326         DisplayComment(currentMove - 1, commentList[currentMove]);
11327     }
11328     if (!matchMode && appData.timeDelay != 0)
11329       DrawPosition(FALSE, boards[currentMove]);
11330
11331     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11332       programStats.ok_to_send = 1;
11333     }
11334
11335     /* if the first token after the PGN tags is a move
11336      * and not move number 1, retrieve it from the parser
11337      */
11338     if (cm != MoveNumberOne)
11339         LoadGameOneMove(cm);
11340
11341     /* load the remaining moves from the file */
11342     while (LoadGameOneMove(EndOfFile)) {
11343       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11344       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11345     }
11346
11347     /* rewind to the start of the game */
11348     currentMove = backwardMostMove;
11349
11350     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11351
11352     if (oldGameMode == AnalyzeFile ||
11353         oldGameMode == AnalyzeMode) {
11354       AnalyzeFileEvent();
11355     }
11356
11357     if (matchMode || appData.timeDelay == 0) {
11358       ToEndEvent();
11359       gameMode = EditGame;
11360       ModeHighlight();
11361     } else if (appData.timeDelay > 0) {
11362       AutoPlayGameLoop();
11363     }
11364
11365     if (appData.debugMode)
11366         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11367
11368     loadFlag = 0; /* [HGM] true game starts */
11369     return TRUE;
11370 }
11371
11372 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11373 int
11374 ReloadPosition(offset)
11375      int offset;
11376 {
11377     int positionNumber = lastLoadPositionNumber + offset;
11378     if (lastLoadPositionFP == NULL) {
11379         DisplayError(_("No position has been loaded yet"), 0);
11380         return FALSE;
11381     }
11382     if (positionNumber <= 0) {
11383         DisplayError(_("Can't back up any further"), 0);
11384         return FALSE;
11385     }
11386     return LoadPosition(lastLoadPositionFP, positionNumber,
11387                         lastLoadPositionTitle);
11388 }
11389
11390 /* Load the nth position from the given file */
11391 int
11392 LoadPositionFromFile(filename, n, title)
11393      char *filename;
11394      int n;
11395      char *title;
11396 {
11397     FILE *f;
11398     char buf[MSG_SIZ];
11399
11400     if (strcmp(filename, "-") == 0) {
11401         return LoadPosition(stdin, n, "stdin");
11402     } else {
11403         f = fopen(filename, "rb");
11404         if (f == NULL) {
11405             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11406             DisplayError(buf, errno);
11407             return FALSE;
11408         } else {
11409             return LoadPosition(f, n, title);
11410         }
11411     }
11412 }
11413
11414 /* Load the nth position from the given open file, and close it */
11415 int
11416 LoadPosition(f, positionNumber, title)
11417      FILE *f;
11418      int positionNumber;
11419      char *title;
11420 {
11421     char *p, line[MSG_SIZ];
11422     Board initial_position;
11423     int i, j, fenMode, pn;
11424
11425     if (gameMode == Training )
11426         SetTrainingModeOff();
11427
11428     if (gameMode != BeginningOfGame) {
11429         Reset(FALSE, TRUE);
11430     }
11431     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11432         fclose(lastLoadPositionFP);
11433     }
11434     if (positionNumber == 0) positionNumber = 1;
11435     lastLoadPositionFP = f;
11436     lastLoadPositionNumber = positionNumber;
11437     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11438     if (first.pr == NoProc) {
11439       StartChessProgram(&first);
11440       InitChessProgram(&first, FALSE);
11441     }
11442     pn = positionNumber;
11443     if (positionNumber < 0) {
11444         /* Negative position number means to seek to that byte offset */
11445         if (fseek(f, -positionNumber, 0) == -1) {
11446             DisplayError(_("Can't seek on position file"), 0);
11447             return FALSE;
11448         };
11449         pn = 1;
11450     } else {
11451         if (fseek(f, 0, 0) == -1) {
11452             if (f == lastLoadPositionFP ?
11453                 positionNumber == lastLoadPositionNumber + 1 :
11454                 positionNumber == 1) {
11455                 pn = 1;
11456             } else {
11457                 DisplayError(_("Can't seek on position file"), 0);
11458                 return FALSE;
11459             }
11460         }
11461     }
11462     /* See if this file is FEN or old-style xboard */
11463     if (fgets(line, MSG_SIZ, f) == NULL) {
11464         DisplayError(_("Position not found in file"), 0);
11465         return FALSE;
11466     }
11467     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11468     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11469
11470     if (pn >= 2) {
11471         if (fenMode || line[0] == '#') pn--;
11472         while (pn > 0) {
11473             /* skip positions before number pn */
11474             if (fgets(line, MSG_SIZ, f) == NULL) {
11475                 Reset(TRUE, TRUE);
11476                 DisplayError(_("Position not found in file"), 0);
11477                 return FALSE;
11478             }
11479             if (fenMode || line[0] == '#') pn--;
11480         }
11481     }
11482
11483     if (fenMode) {
11484         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11485             DisplayError(_("Bad FEN position in file"), 0);
11486             return FALSE;
11487         }
11488     } else {
11489         (void) fgets(line, MSG_SIZ, f);
11490         (void) fgets(line, MSG_SIZ, f);
11491
11492         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11493             (void) fgets(line, MSG_SIZ, f);
11494             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11495                 if (*p == ' ')
11496                   continue;
11497                 initial_position[i][j++] = CharToPiece(*p);
11498             }
11499         }
11500
11501         blackPlaysFirst = FALSE;
11502         if (!feof(f)) {
11503             (void) fgets(line, MSG_SIZ, f);
11504             if (strncmp(line, "black", strlen("black"))==0)
11505               blackPlaysFirst = TRUE;
11506         }
11507     }
11508     startedFromSetupPosition = TRUE;
11509
11510     SendToProgram("force\n", &first);
11511     CopyBoard(boards[0], initial_position);
11512     if (blackPlaysFirst) {
11513         currentMove = forwardMostMove = backwardMostMove = 1;
11514         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11515         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11516         CopyBoard(boards[1], initial_position);
11517         DisplayMessage("", _("Black to play"));
11518     } else {
11519         currentMove = forwardMostMove = backwardMostMove = 0;
11520         DisplayMessage("", _("White to play"));
11521     }
11522     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11523     SendBoard(&first, forwardMostMove);
11524     if (appData.debugMode) {
11525 int i, j;
11526   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11527   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11528         fprintf(debugFP, "Load Position\n");
11529     }
11530
11531     if (positionNumber > 1) {
11532       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11533         DisplayTitle(line);
11534     } else {
11535         DisplayTitle(title);
11536     }
11537     gameMode = EditGame;
11538     ModeHighlight();
11539     ResetClocks();
11540     timeRemaining[0][1] = whiteTimeRemaining;
11541     timeRemaining[1][1] = blackTimeRemaining;
11542     DrawPosition(FALSE, boards[currentMove]);
11543
11544     return TRUE;
11545 }
11546
11547
11548 void
11549 CopyPlayerNameIntoFileName(dest, src)
11550      char **dest, *src;
11551 {
11552     while (*src != NULLCHAR && *src != ',') {
11553         if (*src == ' ') {
11554             *(*dest)++ = '_';
11555             src++;
11556         } else {
11557             *(*dest)++ = *src++;
11558         }
11559     }
11560 }
11561
11562 char *DefaultFileName(ext)
11563      char *ext;
11564 {
11565     static char def[MSG_SIZ];
11566     char *p;
11567
11568     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11569         p = def;
11570         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11571         *p++ = '-';
11572         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11573         *p++ = '.';
11574         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11575     } else {
11576         def[0] = NULLCHAR;
11577     }
11578     return def;
11579 }
11580
11581 /* Save the current game to the given file */
11582 int
11583 SaveGameToFile(filename, append)
11584      char *filename;
11585      int append;
11586 {
11587     FILE *f;
11588     char buf[MSG_SIZ];
11589     int result;
11590
11591     if (strcmp(filename, "-") == 0) {
11592         return SaveGame(stdout, 0, NULL);
11593     } else {
11594         f = fopen(filename, append ? "a" : "w");
11595         if (f == NULL) {
11596             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11597             DisplayError(buf, errno);
11598             return FALSE;
11599         } else {
11600             safeStrCpy(buf, lastMsg, MSG_SIZ);
11601             DisplayMessage(_("Waiting for access to save file"), "");
11602             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11603             DisplayMessage(_("Saving game"), "");
11604             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11605             result = SaveGame(f, 0, NULL);
11606             DisplayMessage(buf, "");
11607             return result;
11608         }
11609     }
11610 }
11611
11612 char *
11613 SavePart(str)
11614      char *str;
11615 {
11616     static char buf[MSG_SIZ];
11617     char *p;
11618
11619     p = strchr(str, ' ');
11620     if (p == NULL) return str;
11621     strncpy(buf, str, p - str);
11622     buf[p - str] = NULLCHAR;
11623     return buf;
11624 }
11625
11626 #define PGN_MAX_LINE 75
11627
11628 #define PGN_SIDE_WHITE  0
11629 #define PGN_SIDE_BLACK  1
11630
11631 /* [AS] */
11632 static int FindFirstMoveOutOfBook( int side )
11633 {
11634     int result = -1;
11635
11636     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11637         int index = backwardMostMove;
11638         int has_book_hit = 0;
11639
11640         if( (index % 2) != side ) {
11641             index++;
11642         }
11643
11644         while( index < forwardMostMove ) {
11645             /* Check to see if engine is in book */
11646             int depth = pvInfoList[index].depth;
11647             int score = pvInfoList[index].score;
11648             int in_book = 0;
11649
11650             if( depth <= 2 ) {
11651                 in_book = 1;
11652             }
11653             else if( score == 0 && depth == 63 ) {
11654                 in_book = 1; /* Zappa */
11655             }
11656             else if( score == 2 && depth == 99 ) {
11657                 in_book = 1; /* Abrok */
11658             }
11659
11660             has_book_hit += in_book;
11661
11662             if( ! in_book ) {
11663                 result = index;
11664
11665                 break;
11666             }
11667
11668             index += 2;
11669         }
11670     }
11671
11672     return result;
11673 }
11674
11675 /* [AS] */
11676 void GetOutOfBookInfo( char * buf )
11677 {
11678     int oob[2];
11679     int i;
11680     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11681
11682     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11683     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11684
11685     *buf = '\0';
11686
11687     if( oob[0] >= 0 || oob[1] >= 0 ) {
11688         for( i=0; i<2; i++ ) {
11689             int idx = oob[i];
11690
11691             if( idx >= 0 ) {
11692                 if( i > 0 && oob[0] >= 0 ) {
11693                     strcat( buf, "   " );
11694                 }
11695
11696                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11697                 sprintf( buf+strlen(buf), "%s%.2f",
11698                     pvInfoList[idx].score >= 0 ? "+" : "",
11699                     pvInfoList[idx].score / 100.0 );
11700             }
11701         }
11702     }
11703 }
11704
11705 /* Save game in PGN style and close the file */
11706 int
11707 SaveGamePGN(f)
11708      FILE *f;
11709 {
11710     int i, offset, linelen, newblock;
11711     time_t tm;
11712 //    char *movetext;
11713     char numtext[32];
11714     int movelen, numlen, blank;
11715     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11716
11717     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11718
11719     tm = time((time_t *) NULL);
11720
11721     PrintPGNTags(f, &gameInfo);
11722
11723     if (backwardMostMove > 0 || startedFromSetupPosition) {
11724         char *fen = PositionToFEN(backwardMostMove, NULL);
11725         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11726         fprintf(f, "\n{--------------\n");
11727         PrintPosition(f, backwardMostMove);
11728         fprintf(f, "--------------}\n");
11729         free(fen);
11730     }
11731     else {
11732         /* [AS] Out of book annotation */
11733         if( appData.saveOutOfBookInfo ) {
11734             char buf[64];
11735
11736             GetOutOfBookInfo( buf );
11737
11738             if( buf[0] != '\0' ) {
11739                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11740             }
11741         }
11742
11743         fprintf(f, "\n");
11744     }
11745
11746     i = backwardMostMove;
11747     linelen = 0;
11748     newblock = TRUE;
11749
11750     while (i < forwardMostMove) {
11751         /* Print comments preceding this move */
11752         if (commentList[i] != NULL) {
11753             if (linelen > 0) fprintf(f, "\n");
11754             fprintf(f, "%s", commentList[i]);
11755             linelen = 0;
11756             newblock = TRUE;
11757         }
11758
11759         /* Format move number */
11760         if ((i % 2) == 0)
11761           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11762         else
11763           if (newblock)
11764             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11765           else
11766             numtext[0] = NULLCHAR;
11767
11768         numlen = strlen(numtext);
11769         newblock = FALSE;
11770
11771         /* Print move number */
11772         blank = linelen > 0 && numlen > 0;
11773         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11774             fprintf(f, "\n");
11775             linelen = 0;
11776             blank = 0;
11777         }
11778         if (blank) {
11779             fprintf(f, " ");
11780             linelen++;
11781         }
11782         fprintf(f, "%s", numtext);
11783         linelen += numlen;
11784
11785         /* Get move */
11786         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11787         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11788
11789         /* Print move */
11790         blank = linelen > 0 && movelen > 0;
11791         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11792             fprintf(f, "\n");
11793             linelen = 0;
11794             blank = 0;
11795         }
11796         if (blank) {
11797             fprintf(f, " ");
11798             linelen++;
11799         }
11800         fprintf(f, "%s", move_buffer);
11801         linelen += movelen;
11802
11803         /* [AS] Add PV info if present */
11804         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11805             /* [HGM] add time */
11806             char buf[MSG_SIZ]; int seconds;
11807
11808             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11809
11810             if( seconds <= 0)
11811               buf[0] = 0;
11812             else
11813               if( seconds < 30 )
11814                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11815               else
11816                 {
11817                   seconds = (seconds + 4)/10; // round to full seconds
11818                   if( seconds < 60 )
11819                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11820                   else
11821                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11822                 }
11823
11824             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11825                       pvInfoList[i].score >= 0 ? "+" : "",
11826                       pvInfoList[i].score / 100.0,
11827                       pvInfoList[i].depth,
11828                       buf );
11829
11830             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11831
11832             /* Print score/depth */
11833             blank = linelen > 0 && movelen > 0;
11834             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11835                 fprintf(f, "\n");
11836                 linelen = 0;
11837                 blank = 0;
11838             }
11839             if (blank) {
11840                 fprintf(f, " ");
11841                 linelen++;
11842             }
11843             fprintf(f, "%s", move_buffer);
11844             linelen += movelen;
11845         }
11846
11847         i++;
11848     }
11849
11850     /* Start a new line */
11851     if (linelen > 0) fprintf(f, "\n");
11852
11853     /* Print comments after last move */
11854     if (commentList[i] != NULL) {
11855         fprintf(f, "%s\n", commentList[i]);
11856     }
11857
11858     /* Print result */
11859     if (gameInfo.resultDetails != NULL &&
11860         gameInfo.resultDetails[0] != NULLCHAR) {
11861         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11862                 PGNResult(gameInfo.result));
11863     } else {
11864         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11865     }
11866
11867     fclose(f);
11868     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11869     return TRUE;
11870 }
11871
11872 /* Save game in old style and close the file */
11873 int
11874 SaveGameOldStyle(f)
11875      FILE *f;
11876 {
11877     int i, offset;
11878     time_t tm;
11879
11880     tm = time((time_t *) NULL);
11881
11882     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11883     PrintOpponents(f);
11884
11885     if (backwardMostMove > 0 || startedFromSetupPosition) {
11886         fprintf(f, "\n[--------------\n");
11887         PrintPosition(f, backwardMostMove);
11888         fprintf(f, "--------------]\n");
11889     } else {
11890         fprintf(f, "\n");
11891     }
11892
11893     i = backwardMostMove;
11894     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11895
11896     while (i < forwardMostMove) {
11897         if (commentList[i] != NULL) {
11898             fprintf(f, "[%s]\n", commentList[i]);
11899         }
11900
11901         if ((i % 2) == 1) {
11902             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11903             i++;
11904         } else {
11905             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11906             i++;
11907             if (commentList[i] != NULL) {
11908                 fprintf(f, "\n");
11909                 continue;
11910             }
11911             if (i >= forwardMostMove) {
11912                 fprintf(f, "\n");
11913                 break;
11914             }
11915             fprintf(f, "%s\n", parseList[i]);
11916             i++;
11917         }
11918     }
11919
11920     if (commentList[i] != NULL) {
11921         fprintf(f, "[%s]\n", commentList[i]);
11922     }
11923
11924     /* This isn't really the old style, but it's close enough */
11925     if (gameInfo.resultDetails != NULL &&
11926         gameInfo.resultDetails[0] != NULLCHAR) {
11927         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11928                 gameInfo.resultDetails);
11929     } else {
11930         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11931     }
11932
11933     fclose(f);
11934     return TRUE;
11935 }
11936
11937 /* Save the current game to open file f and close the file */
11938 int
11939 SaveGame(f, dummy, dummy2)
11940      FILE *f;
11941      int dummy;
11942      char *dummy2;
11943 {
11944     if (gameMode == EditPosition) EditPositionDone(TRUE);
11945     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11946     if (appData.oldSaveStyle)
11947       return SaveGameOldStyle(f);
11948     else
11949       return SaveGamePGN(f);
11950 }
11951
11952 /* Save the current position to the given file */
11953 int
11954 SavePositionToFile(filename)
11955      char *filename;
11956 {
11957     FILE *f;
11958     char buf[MSG_SIZ];
11959
11960     if (strcmp(filename, "-") == 0) {
11961         return SavePosition(stdout, 0, NULL);
11962     } else {
11963         f = fopen(filename, "a");
11964         if (f == NULL) {
11965             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11966             DisplayError(buf, errno);
11967             return FALSE;
11968         } else {
11969             safeStrCpy(buf, lastMsg, MSG_SIZ);
11970             DisplayMessage(_("Waiting for access to save file"), "");
11971             flock(fileno(f), LOCK_EX); // [HGM] lock
11972             DisplayMessage(_("Saving position"), "");
11973             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11974             SavePosition(f, 0, NULL);
11975             DisplayMessage(buf, "");
11976             return TRUE;
11977         }
11978     }
11979 }
11980
11981 /* Save the current position to the given open file and close the file */
11982 int
11983 SavePosition(f, dummy, dummy2)
11984      FILE *f;
11985      int dummy;
11986      char *dummy2;
11987 {
11988     time_t tm;
11989     char *fen;
11990
11991     if (gameMode == EditPosition) EditPositionDone(TRUE);
11992     if (appData.oldSaveStyle) {
11993         tm = time((time_t *) NULL);
11994
11995         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11996         PrintOpponents(f);
11997         fprintf(f, "[--------------\n");
11998         PrintPosition(f, currentMove);
11999         fprintf(f, "--------------]\n");
12000     } else {
12001         fen = PositionToFEN(currentMove, NULL);
12002         fprintf(f, "%s\n", fen);
12003         free(fen);
12004     }
12005     fclose(f);
12006     return TRUE;
12007 }
12008
12009 void
12010 ReloadCmailMsgEvent(unregister)
12011      int unregister;
12012 {
12013 #if !WIN32
12014     static char *inFilename = NULL;
12015     static char *outFilename;
12016     int i;
12017     struct stat inbuf, outbuf;
12018     int status;
12019
12020     /* Any registered moves are unregistered if unregister is set, */
12021     /* i.e. invoked by the signal handler */
12022     if (unregister) {
12023         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12024             cmailMoveRegistered[i] = FALSE;
12025             if (cmailCommentList[i] != NULL) {
12026                 free(cmailCommentList[i]);
12027                 cmailCommentList[i] = NULL;
12028             }
12029         }
12030         nCmailMovesRegistered = 0;
12031     }
12032
12033     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12034         cmailResult[i] = CMAIL_NOT_RESULT;
12035     }
12036     nCmailResults = 0;
12037
12038     if (inFilename == NULL) {
12039         /* Because the filenames are static they only get malloced once  */
12040         /* and they never get freed                                      */
12041         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12042         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12043
12044         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12045         sprintf(outFilename, "%s.out", appData.cmailGameName);
12046     }
12047
12048     status = stat(outFilename, &outbuf);
12049     if (status < 0) {
12050         cmailMailedMove = FALSE;
12051     } else {
12052         status = stat(inFilename, &inbuf);
12053         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12054     }
12055
12056     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12057        counts the games, notes how each one terminated, etc.
12058
12059        It would be nice to remove this kludge and instead gather all
12060        the information while building the game list.  (And to keep it
12061        in the game list nodes instead of having a bunch of fixed-size
12062        parallel arrays.)  Note this will require getting each game's
12063        termination from the PGN tags, as the game list builder does
12064        not process the game moves.  --mann
12065        */
12066     cmailMsgLoaded = TRUE;
12067     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12068
12069     /* Load first game in the file or popup game menu */
12070     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12071
12072 #endif /* !WIN32 */
12073     return;
12074 }
12075
12076 int
12077 RegisterMove()
12078 {
12079     FILE *f;
12080     char string[MSG_SIZ];
12081
12082     if (   cmailMailedMove
12083         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12084         return TRUE;            /* Allow free viewing  */
12085     }
12086
12087     /* Unregister move to ensure that we don't leave RegisterMove        */
12088     /* with the move registered when the conditions for registering no   */
12089     /* longer hold                                                       */
12090     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12091         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12092         nCmailMovesRegistered --;
12093
12094         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12095           {
12096               free(cmailCommentList[lastLoadGameNumber - 1]);
12097               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12098           }
12099     }
12100
12101     if (cmailOldMove == -1) {
12102         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12103         return FALSE;
12104     }
12105
12106     if (currentMove > cmailOldMove + 1) {
12107         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12108         return FALSE;
12109     }
12110
12111     if (currentMove < cmailOldMove) {
12112         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12113         return FALSE;
12114     }
12115
12116     if (forwardMostMove > currentMove) {
12117         /* Silently truncate extra moves */
12118         TruncateGame();
12119     }
12120
12121     if (   (currentMove == cmailOldMove + 1)
12122         || (   (currentMove == cmailOldMove)
12123             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12124                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12125         if (gameInfo.result != GameUnfinished) {
12126             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12127         }
12128
12129         if (commentList[currentMove] != NULL) {
12130             cmailCommentList[lastLoadGameNumber - 1]
12131               = StrSave(commentList[currentMove]);
12132         }
12133         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12134
12135         if (appData.debugMode)
12136           fprintf(debugFP, "Saving %s for game %d\n",
12137                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12138
12139         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12140
12141         f = fopen(string, "w");
12142         if (appData.oldSaveStyle) {
12143             SaveGameOldStyle(f); /* also closes the file */
12144
12145             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12146             f = fopen(string, "w");
12147             SavePosition(f, 0, NULL); /* also closes the file */
12148         } else {
12149             fprintf(f, "{--------------\n");
12150             PrintPosition(f, currentMove);
12151             fprintf(f, "--------------}\n\n");
12152
12153             SaveGame(f, 0, NULL); /* also closes the file*/
12154         }
12155
12156         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12157         nCmailMovesRegistered ++;
12158     } else if (nCmailGames == 1) {
12159         DisplayError(_("You have not made a move yet"), 0);
12160         return FALSE;
12161     }
12162
12163     return TRUE;
12164 }
12165
12166 void
12167 MailMoveEvent()
12168 {
12169 #if !WIN32
12170     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12171     FILE *commandOutput;
12172     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12173     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12174     int nBuffers;
12175     int i;
12176     int archived;
12177     char *arcDir;
12178
12179     if (! cmailMsgLoaded) {
12180         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12181         return;
12182     }
12183
12184     if (nCmailGames == nCmailResults) {
12185         DisplayError(_("No unfinished games"), 0);
12186         return;
12187     }
12188
12189 #if CMAIL_PROHIBIT_REMAIL
12190     if (cmailMailedMove) {
12191       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);
12192         DisplayError(msg, 0);
12193         return;
12194     }
12195 #endif
12196
12197     if (! (cmailMailedMove || RegisterMove())) return;
12198
12199     if (   cmailMailedMove
12200         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12201       snprintf(string, MSG_SIZ, partCommandString,
12202                appData.debugMode ? " -v" : "", appData.cmailGameName);
12203         commandOutput = popen(string, "r");
12204
12205         if (commandOutput == NULL) {
12206             DisplayError(_("Failed to invoke cmail"), 0);
12207         } else {
12208             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12209                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12210             }
12211             if (nBuffers > 1) {
12212                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12213                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12214                 nBytes = MSG_SIZ - 1;
12215             } else {
12216                 (void) memcpy(msg, buffer, nBytes);
12217             }
12218             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12219
12220             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12221                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12222
12223                 archived = TRUE;
12224                 for (i = 0; i < nCmailGames; i ++) {
12225                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12226                         archived = FALSE;
12227                     }
12228                 }
12229                 if (   archived
12230                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12231                         != NULL)) {
12232                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12233                            arcDir,
12234                            appData.cmailGameName,
12235                            gameInfo.date);
12236                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12237                     cmailMsgLoaded = FALSE;
12238                 }
12239             }
12240
12241             DisplayInformation(msg);
12242             pclose(commandOutput);
12243         }
12244     } else {
12245         if ((*cmailMsg) != '\0') {
12246             DisplayInformation(cmailMsg);
12247         }
12248     }
12249
12250     return;
12251 #endif /* !WIN32 */
12252 }
12253
12254 char *
12255 CmailMsg()
12256 {
12257 #if WIN32
12258     return NULL;
12259 #else
12260     int  prependComma = 0;
12261     char number[5];
12262     char string[MSG_SIZ];       /* Space for game-list */
12263     int  i;
12264
12265     if (!cmailMsgLoaded) return "";
12266
12267     if (cmailMailedMove) {
12268       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12269     } else {
12270         /* Create a list of games left */
12271       snprintf(string, MSG_SIZ, "[");
12272         for (i = 0; i < nCmailGames; i ++) {
12273             if (! (   cmailMoveRegistered[i]
12274                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12275                 if (prependComma) {
12276                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12277                 } else {
12278                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12279                     prependComma = 1;
12280                 }
12281
12282                 strcat(string, number);
12283             }
12284         }
12285         strcat(string, "]");
12286
12287         if (nCmailMovesRegistered + nCmailResults == 0) {
12288             switch (nCmailGames) {
12289               case 1:
12290                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12291                 break;
12292
12293               case 2:
12294                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12295                 break;
12296
12297               default:
12298                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12299                          nCmailGames);
12300                 break;
12301             }
12302         } else {
12303             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12304               case 1:
12305                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12306                          string);
12307                 break;
12308
12309               case 0:
12310                 if (nCmailResults == nCmailGames) {
12311                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12312                 } else {
12313                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12314                 }
12315                 break;
12316
12317               default:
12318                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12319                          string);
12320             }
12321         }
12322     }
12323     return cmailMsg;
12324 #endif /* WIN32 */
12325 }
12326
12327 void
12328 ResetGameEvent()
12329 {
12330     if (gameMode == Training)
12331       SetTrainingModeOff();
12332
12333     Reset(TRUE, TRUE);
12334     cmailMsgLoaded = FALSE;
12335     if (appData.icsActive) {
12336       SendToICS(ics_prefix);
12337       SendToICS("refresh\n");
12338     }
12339 }
12340
12341 void
12342 ExitEvent(status)
12343      int status;
12344 {
12345     exiting++;
12346     if (exiting > 2) {
12347       /* Give up on clean exit */
12348       exit(status);
12349     }
12350     if (exiting > 1) {
12351       /* Keep trying for clean exit */
12352       return;
12353     }
12354
12355     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12356
12357     if (telnetISR != NULL) {
12358       RemoveInputSource(telnetISR);
12359     }
12360     if (icsPR != NoProc) {
12361       DestroyChildProcess(icsPR, TRUE);
12362     }
12363
12364     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12365     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12366
12367     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12368     /* make sure this other one finishes before killing it!                  */
12369     if(endingGame) { int count = 0;
12370         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12371         while(endingGame && count++ < 10) DoSleep(1);
12372         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12373     }
12374
12375     /* Kill off chess programs */
12376     if (first.pr != NoProc) {
12377         ExitAnalyzeMode();
12378
12379         DoSleep( appData.delayBeforeQuit );
12380         SendToProgram("quit\n", &first);
12381         DoSleep( appData.delayAfterQuit );
12382         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12383     }
12384     if (second.pr != NoProc) {
12385         DoSleep( appData.delayBeforeQuit );
12386         SendToProgram("quit\n", &second);
12387         DoSleep( appData.delayAfterQuit );
12388         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12389     }
12390     if (first.isr != NULL) {
12391         RemoveInputSource(first.isr);
12392     }
12393     if (second.isr != NULL) {
12394         RemoveInputSource(second.isr);
12395     }
12396
12397     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12398     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12399
12400     ShutDownFrontEnd();
12401     exit(status);
12402 }
12403
12404 void
12405 PauseEvent()
12406 {
12407     if (appData.debugMode)
12408         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12409     if (pausing) {
12410         pausing = FALSE;
12411         ModeHighlight();
12412         if (gameMode == MachinePlaysWhite ||
12413             gameMode == MachinePlaysBlack) {
12414             StartClocks();
12415         } else {
12416             DisplayBothClocks();
12417         }
12418         if (gameMode == PlayFromGameFile) {
12419             if (appData.timeDelay >= 0)
12420                 AutoPlayGameLoop();
12421         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12422             Reset(FALSE, TRUE);
12423             SendToICS(ics_prefix);
12424             SendToICS("refresh\n");
12425         } else if (currentMove < forwardMostMove) {
12426             ForwardInner(forwardMostMove);
12427         }
12428         pauseExamInvalid = FALSE;
12429     } else {
12430         switch (gameMode) {
12431           default:
12432             return;
12433           case IcsExamining:
12434             pauseExamForwardMostMove = forwardMostMove;
12435             pauseExamInvalid = FALSE;
12436             /* fall through */
12437           case IcsObserving:
12438           case IcsPlayingWhite:
12439           case IcsPlayingBlack:
12440             pausing = TRUE;
12441             ModeHighlight();
12442             return;
12443           case PlayFromGameFile:
12444             (void) StopLoadGameTimer();
12445             pausing = TRUE;
12446             ModeHighlight();
12447             break;
12448           case BeginningOfGame:
12449             if (appData.icsActive) return;
12450             /* else fall through */
12451           case MachinePlaysWhite:
12452           case MachinePlaysBlack:
12453           case TwoMachinesPlay:
12454             if (forwardMostMove == 0)
12455               return;           /* don't pause if no one has moved */
12456             if ((gameMode == MachinePlaysWhite &&
12457                  !WhiteOnMove(forwardMostMove)) ||
12458                 (gameMode == MachinePlaysBlack &&
12459                  WhiteOnMove(forwardMostMove))) {
12460                 StopClocks();
12461             }
12462             pausing = TRUE;
12463             ModeHighlight();
12464             break;
12465         }
12466     }
12467 }
12468
12469 void
12470 EditCommentEvent()
12471 {
12472     char title[MSG_SIZ];
12473
12474     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12475       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12476     } else {
12477       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12478                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12479                parseList[currentMove - 1]);
12480     }
12481
12482     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12483 }
12484
12485
12486 void
12487 EditTagsEvent()
12488 {
12489     char *tags = PGNTags(&gameInfo);
12490     bookUp = FALSE;
12491     EditTagsPopUp(tags, NULL);
12492     free(tags);
12493 }
12494
12495 void
12496 AnalyzeModeEvent()
12497 {
12498     if (appData.noChessProgram || gameMode == AnalyzeMode)
12499       return;
12500
12501     if (gameMode != AnalyzeFile) {
12502         if (!appData.icsEngineAnalyze) {
12503                EditGameEvent();
12504                if (gameMode != EditGame) return;
12505         }
12506         ResurrectChessProgram();
12507         SendToProgram("analyze\n", &first);
12508         first.analyzing = TRUE;
12509         /*first.maybeThinking = TRUE;*/
12510         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12511         EngineOutputPopUp();
12512     }
12513     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12514     pausing = FALSE;
12515     ModeHighlight();
12516     SetGameInfo();
12517
12518     StartAnalysisClock();
12519     GetTimeMark(&lastNodeCountTime);
12520     lastNodeCount = 0;
12521 }
12522
12523 void
12524 AnalyzeFileEvent()
12525 {
12526     if (appData.noChessProgram || gameMode == AnalyzeFile)
12527       return;
12528
12529     if (gameMode != AnalyzeMode) {
12530         EditGameEvent();
12531         if (gameMode != EditGame) return;
12532         ResurrectChessProgram();
12533         SendToProgram("analyze\n", &first);
12534         first.analyzing = TRUE;
12535         /*first.maybeThinking = TRUE;*/
12536         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12537         EngineOutputPopUp();
12538     }
12539     gameMode = AnalyzeFile;
12540     pausing = FALSE;
12541     ModeHighlight();
12542     SetGameInfo();
12543
12544     StartAnalysisClock();
12545     GetTimeMark(&lastNodeCountTime);
12546     lastNodeCount = 0;
12547 }
12548
12549 void
12550 MachineWhiteEvent()
12551 {
12552     char buf[MSG_SIZ];
12553     char *bookHit = NULL;
12554
12555     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12556       return;
12557
12558
12559     if (gameMode == PlayFromGameFile ||
12560         gameMode == TwoMachinesPlay  ||
12561         gameMode == Training         ||
12562         gameMode == AnalyzeMode      ||
12563         gameMode == EndOfGame)
12564         EditGameEvent();
12565
12566     if (gameMode == EditPosition)
12567         EditPositionDone(TRUE);
12568
12569     if (!WhiteOnMove(currentMove)) {
12570         DisplayError(_("It is not White's turn"), 0);
12571         return;
12572     }
12573
12574     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12575       ExitAnalyzeMode();
12576
12577     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12578         gameMode == AnalyzeFile)
12579         TruncateGame();
12580
12581     ResurrectChessProgram();    /* in case it isn't running */
12582     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12583         gameMode = MachinePlaysWhite;
12584         ResetClocks();
12585     } else
12586     gameMode = MachinePlaysWhite;
12587     pausing = FALSE;
12588     ModeHighlight();
12589     SetGameInfo();
12590     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12591     DisplayTitle(buf);
12592     if (first.sendName) {
12593       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12594       SendToProgram(buf, &first);
12595     }
12596     if (first.sendTime) {
12597       if (first.useColors) {
12598         SendToProgram("black\n", &first); /*gnu kludge*/
12599       }
12600       SendTimeRemaining(&first, TRUE);
12601     }
12602     if (first.useColors) {
12603       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12604     }
12605     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12606     SetMachineThinkingEnables();
12607     first.maybeThinking = TRUE;
12608     StartClocks();
12609     firstMove = FALSE;
12610
12611     if (appData.autoFlipView && !flipView) {
12612       flipView = !flipView;
12613       DrawPosition(FALSE, NULL);
12614       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12615     }
12616
12617     if(bookHit) { // [HGM] book: simulate book reply
12618         static char bookMove[MSG_SIZ]; // a bit generous?
12619
12620         programStats.nodes = programStats.depth = programStats.time =
12621         programStats.score = programStats.got_only_move = 0;
12622         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12623
12624         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12625         strcat(bookMove, bookHit);
12626         HandleMachineMove(bookMove, &first);
12627     }
12628 }
12629
12630 void
12631 MachineBlackEvent()
12632 {
12633   char buf[MSG_SIZ];
12634   char *bookHit = NULL;
12635
12636     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12637         return;
12638
12639
12640     if (gameMode == PlayFromGameFile ||
12641         gameMode == TwoMachinesPlay  ||
12642         gameMode == Training         ||
12643         gameMode == AnalyzeMode      ||
12644         gameMode == EndOfGame)
12645         EditGameEvent();
12646
12647     if (gameMode == EditPosition)
12648         EditPositionDone(TRUE);
12649
12650     if (WhiteOnMove(currentMove)) {
12651         DisplayError(_("It is not Black's turn"), 0);
12652         return;
12653     }
12654
12655     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12656       ExitAnalyzeMode();
12657
12658     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12659         gameMode == AnalyzeFile)
12660         TruncateGame();
12661
12662     ResurrectChessProgram();    /* in case it isn't running */
12663     gameMode = MachinePlaysBlack;
12664     pausing = FALSE;
12665     ModeHighlight();
12666     SetGameInfo();
12667     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12668     DisplayTitle(buf);
12669     if (first.sendName) {
12670       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12671       SendToProgram(buf, &first);
12672     }
12673     if (first.sendTime) {
12674       if (first.useColors) {
12675         SendToProgram("white\n", &first); /*gnu kludge*/
12676       }
12677       SendTimeRemaining(&first, FALSE);
12678     }
12679     if (first.useColors) {
12680       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12681     }
12682     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12683     SetMachineThinkingEnables();
12684     first.maybeThinking = TRUE;
12685     StartClocks();
12686
12687     if (appData.autoFlipView && flipView) {
12688       flipView = !flipView;
12689       DrawPosition(FALSE, NULL);
12690       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12691     }
12692     if(bookHit) { // [HGM] book: simulate book reply
12693         static char bookMove[MSG_SIZ]; // a bit generous?
12694
12695         programStats.nodes = programStats.depth = programStats.time =
12696         programStats.score = programStats.got_only_move = 0;
12697         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12698
12699         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12700         strcat(bookMove, bookHit);
12701         HandleMachineMove(bookMove, &first);
12702     }
12703 }
12704
12705
12706 void
12707 DisplayTwoMachinesTitle()
12708 {
12709     char buf[MSG_SIZ];
12710     if (appData.matchGames > 0) {
12711         if(appData.tourneyFile[0]) {
12712           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12713                    gameInfo.white, gameInfo.black,
12714                    nextGame+1, appData.matchGames+1,
12715                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12716         } else 
12717         if (first.twoMachinesColor[0] == 'w') {
12718           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12719                    gameInfo.white, gameInfo.black,
12720                    first.matchWins, second.matchWins,
12721                    matchGame - 1 - (first.matchWins + second.matchWins));
12722         } else {
12723           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12724                    gameInfo.white, gameInfo.black,
12725                    second.matchWins, first.matchWins,
12726                    matchGame - 1 - (first.matchWins + second.matchWins));
12727         }
12728     } else {
12729       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12730     }
12731     DisplayTitle(buf);
12732 }
12733
12734 void
12735 SettingsMenuIfReady()
12736 {
12737   if (second.lastPing != second.lastPong) {
12738     DisplayMessage("", _("Waiting for second chess program"));
12739     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12740     return;
12741   }
12742   ThawUI();
12743   DisplayMessage("", "");
12744   SettingsPopUp(&second);
12745 }
12746
12747 int
12748 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12749 {
12750     char buf[MSG_SIZ];
12751     if (cps->pr == NULL) {
12752         StartChessProgram(cps);
12753         if (cps->protocolVersion == 1) {
12754           retry();
12755         } else {
12756           /* kludge: allow timeout for initial "feature" command */
12757           FreezeUI();
12758           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12759           DisplayMessage("", buf);
12760           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12761         }
12762         return 1;
12763     }
12764     return 0;
12765 }
12766
12767 void
12768 TwoMachinesEvent P((void))
12769 {
12770     int i;
12771     char buf[MSG_SIZ];
12772     ChessProgramState *onmove;
12773     char *bookHit = NULL;
12774     static int stalling = 0;
12775     TimeMark now;
12776     long wait;
12777
12778     if (appData.noChessProgram) return;
12779
12780     switch (gameMode) {
12781       case TwoMachinesPlay:
12782         return;
12783       case MachinePlaysWhite:
12784       case MachinePlaysBlack:
12785         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12786             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12787             return;
12788         }
12789         /* fall through */
12790       case BeginningOfGame:
12791       case PlayFromGameFile:
12792       case EndOfGame:
12793         EditGameEvent();
12794         if (gameMode != EditGame) return;
12795         break;
12796       case EditPosition:
12797         EditPositionDone(TRUE);
12798         break;
12799       case AnalyzeMode:
12800       case AnalyzeFile:
12801         ExitAnalyzeMode();
12802         break;
12803       case EditGame:
12804       default:
12805         break;
12806     }
12807
12808 //    forwardMostMove = currentMove;
12809     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12810
12811     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12812
12813     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12814     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12815       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12816       return;
12817     }
12818     if(!stalling) {
12819       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12820       SendToProgram("force\n", &second);
12821       stalling = 1;
12822       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12823       return;
12824     }
12825     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12826     if(appData.matchPause>10000 || appData.matchPause<10)
12827                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12828     wait = SubtractTimeMarks(&now, &pauseStart);
12829     if(wait < appData.matchPause) {
12830         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12831         return;
12832     }
12833     stalling = 0;
12834     DisplayMessage("", "");
12835     if (startedFromSetupPosition) {
12836         SendBoard(&second, backwardMostMove);
12837     if (appData.debugMode) {
12838         fprintf(debugFP, "Two Machines\n");
12839     }
12840     }
12841     for (i = backwardMostMove; i < forwardMostMove; i++) {
12842         SendMoveToProgram(i, &second);
12843     }
12844
12845     gameMode = TwoMachinesPlay;
12846     pausing = FALSE;
12847     ModeHighlight();
12848     SetGameInfo();
12849     DisplayTwoMachinesTitle();
12850     firstMove = TRUE;
12851     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12852         onmove = &first;
12853     } else {
12854         onmove = &second;
12855     }
12856     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12857     SendToProgram(first.computerString, &first);
12858     if (first.sendName) {
12859       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12860       SendToProgram(buf, &first);
12861     }
12862     SendToProgram(second.computerString, &second);
12863     if (second.sendName) {
12864       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12865       SendToProgram(buf, &second);
12866     }
12867
12868     ResetClocks();
12869     if (!first.sendTime || !second.sendTime) {
12870         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12871         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12872     }
12873     if (onmove->sendTime) {
12874       if (onmove->useColors) {
12875         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12876       }
12877       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12878     }
12879     if (onmove->useColors) {
12880       SendToProgram(onmove->twoMachinesColor, onmove);
12881     }
12882     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12883 //    SendToProgram("go\n", onmove);
12884     onmove->maybeThinking = TRUE;
12885     SetMachineThinkingEnables();
12886
12887     StartClocks();
12888
12889     if(bookHit) { // [HGM] book: simulate book reply
12890         static char bookMove[MSG_SIZ]; // a bit generous?
12891
12892         programStats.nodes = programStats.depth = programStats.time =
12893         programStats.score = programStats.got_only_move = 0;
12894         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12895
12896         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12897         strcat(bookMove, bookHit);
12898         savedMessage = bookMove; // args for deferred call
12899         savedState = onmove;
12900         ScheduleDelayedEvent(DeferredBookMove, 1);
12901     }
12902 }
12903
12904 void
12905 TrainingEvent()
12906 {
12907     if (gameMode == Training) {
12908       SetTrainingModeOff();
12909       gameMode = PlayFromGameFile;
12910       DisplayMessage("", _("Training mode off"));
12911     } else {
12912       gameMode = Training;
12913       animateTraining = appData.animate;
12914
12915       /* make sure we are not already at the end of the game */
12916       if (currentMove < forwardMostMove) {
12917         SetTrainingModeOn();
12918         DisplayMessage("", _("Training mode on"));
12919       } else {
12920         gameMode = PlayFromGameFile;
12921         DisplayError(_("Already at end of game"), 0);
12922       }
12923     }
12924     ModeHighlight();
12925 }
12926
12927 void
12928 IcsClientEvent()
12929 {
12930     if (!appData.icsActive) return;
12931     switch (gameMode) {
12932       case IcsPlayingWhite:
12933       case IcsPlayingBlack:
12934       case IcsObserving:
12935       case IcsIdle:
12936       case BeginningOfGame:
12937       case IcsExamining:
12938         return;
12939
12940       case EditGame:
12941         break;
12942
12943       case EditPosition:
12944         EditPositionDone(TRUE);
12945         break;
12946
12947       case AnalyzeMode:
12948       case AnalyzeFile:
12949         ExitAnalyzeMode();
12950         break;
12951
12952       default:
12953         EditGameEvent();
12954         break;
12955     }
12956
12957     gameMode = IcsIdle;
12958     ModeHighlight();
12959     return;
12960 }
12961
12962
12963 void
12964 EditGameEvent()
12965 {
12966     int i;
12967
12968     switch (gameMode) {
12969       case Training:
12970         SetTrainingModeOff();
12971         break;
12972       case MachinePlaysWhite:
12973       case MachinePlaysBlack:
12974       case BeginningOfGame:
12975         SendToProgram("force\n", &first);
12976         SetUserThinkingEnables();
12977         break;
12978       case PlayFromGameFile:
12979         (void) StopLoadGameTimer();
12980         if (gameFileFP != NULL) {
12981             gameFileFP = NULL;
12982         }
12983         break;
12984       case EditPosition:
12985         EditPositionDone(TRUE);
12986         break;
12987       case AnalyzeMode:
12988       case AnalyzeFile:
12989         ExitAnalyzeMode();
12990         SendToProgram("force\n", &first);
12991         break;
12992       case TwoMachinesPlay:
12993         GameEnds(EndOfFile, NULL, GE_PLAYER);
12994         ResurrectChessProgram();
12995         SetUserThinkingEnables();
12996         break;
12997       case EndOfGame:
12998         ResurrectChessProgram();
12999         break;
13000       case IcsPlayingBlack:
13001       case IcsPlayingWhite:
13002         DisplayError(_("Warning: You are still playing a game"), 0);
13003         break;
13004       case IcsObserving:
13005         DisplayError(_("Warning: You are still observing a game"), 0);
13006         break;
13007       case IcsExamining:
13008         DisplayError(_("Warning: You are still examining a game"), 0);
13009         break;
13010       case IcsIdle:
13011         break;
13012       case EditGame:
13013       default:
13014         return;
13015     }
13016
13017     pausing = FALSE;
13018     StopClocks();
13019     first.offeredDraw = second.offeredDraw = 0;
13020
13021     if (gameMode == PlayFromGameFile) {
13022         whiteTimeRemaining = timeRemaining[0][currentMove];
13023         blackTimeRemaining = timeRemaining[1][currentMove];
13024         DisplayTitle("");
13025     }
13026
13027     if (gameMode == MachinePlaysWhite ||
13028         gameMode == MachinePlaysBlack ||
13029         gameMode == TwoMachinesPlay ||
13030         gameMode == EndOfGame) {
13031         i = forwardMostMove;
13032         while (i > currentMove) {
13033             SendToProgram("undo\n", &first);
13034             i--;
13035         }
13036         whiteTimeRemaining = timeRemaining[0][currentMove];
13037         blackTimeRemaining = timeRemaining[1][currentMove];
13038         DisplayBothClocks();
13039         if (whiteFlag || blackFlag) {
13040             whiteFlag = blackFlag = 0;
13041         }
13042         DisplayTitle("");
13043     }
13044
13045     gameMode = EditGame;
13046     ModeHighlight();
13047     SetGameInfo();
13048 }
13049
13050
13051 void
13052 EditPositionEvent()
13053 {
13054     if (gameMode == EditPosition) {
13055         EditGameEvent();
13056         return;
13057     }
13058
13059     EditGameEvent();
13060     if (gameMode != EditGame) return;
13061
13062     gameMode = EditPosition;
13063     ModeHighlight();
13064     SetGameInfo();
13065     if (currentMove > 0)
13066       CopyBoard(boards[0], boards[currentMove]);
13067
13068     blackPlaysFirst = !WhiteOnMove(currentMove);
13069     ResetClocks();
13070     currentMove = forwardMostMove = backwardMostMove = 0;
13071     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13072     DisplayMove(-1);
13073 }
13074
13075 void
13076 ExitAnalyzeMode()
13077 {
13078     /* [DM] icsEngineAnalyze - possible call from other functions */
13079     if (appData.icsEngineAnalyze) {
13080         appData.icsEngineAnalyze = FALSE;
13081
13082         DisplayMessage("",_("Close ICS engine analyze..."));
13083     }
13084     if (first.analysisSupport && first.analyzing) {
13085       SendToProgram("exit\n", &first);
13086       first.analyzing = FALSE;
13087     }
13088     thinkOutput[0] = NULLCHAR;
13089 }
13090
13091 void
13092 EditPositionDone(Boolean fakeRights)
13093 {
13094     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13095
13096     startedFromSetupPosition = TRUE;
13097     InitChessProgram(&first, FALSE);
13098     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13099       boards[0][EP_STATUS] = EP_NONE;
13100       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13101     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13102         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13103         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13104       } else boards[0][CASTLING][2] = NoRights;
13105     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13106         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13107         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13108       } else boards[0][CASTLING][5] = NoRights;
13109     }
13110     SendToProgram("force\n", &first);
13111     if (blackPlaysFirst) {
13112         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13113         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13114         currentMove = forwardMostMove = backwardMostMove = 1;
13115         CopyBoard(boards[1], boards[0]);
13116     } else {
13117         currentMove = forwardMostMove = backwardMostMove = 0;
13118     }
13119     SendBoard(&first, forwardMostMove);
13120     if (appData.debugMode) {
13121         fprintf(debugFP, "EditPosDone\n");
13122     }
13123     DisplayTitle("");
13124     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13125     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13126     gameMode = EditGame;
13127     ModeHighlight();
13128     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13129     ClearHighlights(); /* [AS] */
13130 }
13131
13132 /* Pause for `ms' milliseconds */
13133 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13134 void
13135 TimeDelay(ms)
13136      long ms;
13137 {
13138     TimeMark m1, m2;
13139
13140     GetTimeMark(&m1);
13141     do {
13142         GetTimeMark(&m2);
13143     } while (SubtractTimeMarks(&m2, &m1) < ms);
13144 }
13145
13146 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13147 void
13148 SendMultiLineToICS(buf)
13149      char *buf;
13150 {
13151     char temp[MSG_SIZ+1], *p;
13152     int len;
13153
13154     len = strlen(buf);
13155     if (len > MSG_SIZ)
13156       len = MSG_SIZ;
13157
13158     strncpy(temp, buf, len);
13159     temp[len] = 0;
13160
13161     p = temp;
13162     while (*p) {
13163         if (*p == '\n' || *p == '\r')
13164           *p = ' ';
13165         ++p;
13166     }
13167
13168     strcat(temp, "\n");
13169     SendToICS(temp);
13170     SendToPlayer(temp, strlen(temp));
13171 }
13172
13173 void
13174 SetWhiteToPlayEvent()
13175 {
13176     if (gameMode == EditPosition) {
13177         blackPlaysFirst = FALSE;
13178         DisplayBothClocks();    /* works because currentMove is 0 */
13179     } else if (gameMode == IcsExamining) {
13180         SendToICS(ics_prefix);
13181         SendToICS("tomove white\n");
13182     }
13183 }
13184
13185 void
13186 SetBlackToPlayEvent()
13187 {
13188     if (gameMode == EditPosition) {
13189         blackPlaysFirst = TRUE;
13190         currentMove = 1;        /* kludge */
13191         DisplayBothClocks();
13192         currentMove = 0;
13193     } else if (gameMode == IcsExamining) {
13194         SendToICS(ics_prefix);
13195         SendToICS("tomove black\n");
13196     }
13197 }
13198
13199 void
13200 EditPositionMenuEvent(selection, x, y)
13201      ChessSquare selection;
13202      int x, y;
13203 {
13204     char buf[MSG_SIZ];
13205     ChessSquare piece = boards[0][y][x];
13206
13207     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13208
13209     switch (selection) {
13210       case ClearBoard:
13211         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13212             SendToICS(ics_prefix);
13213             SendToICS("bsetup clear\n");
13214         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13215             SendToICS(ics_prefix);
13216             SendToICS("clearboard\n");
13217         } else {
13218             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13219                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13220                 for (y = 0; y < BOARD_HEIGHT; y++) {
13221                     if (gameMode == IcsExamining) {
13222                         if (boards[currentMove][y][x] != EmptySquare) {
13223                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13224                                     AAA + x, ONE + y);
13225                             SendToICS(buf);
13226                         }
13227                     } else {
13228                         boards[0][y][x] = p;
13229                     }
13230                 }
13231             }
13232         }
13233         if (gameMode == EditPosition) {
13234             DrawPosition(FALSE, boards[0]);
13235         }
13236         break;
13237
13238       case WhitePlay:
13239         SetWhiteToPlayEvent();
13240         break;
13241
13242       case BlackPlay:
13243         SetBlackToPlayEvent();
13244         break;
13245
13246       case EmptySquare:
13247         if (gameMode == IcsExamining) {
13248             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13249             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13250             SendToICS(buf);
13251         } else {
13252             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13253                 if(x == BOARD_LEFT-2) {
13254                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13255                     boards[0][y][1] = 0;
13256                 } else
13257                 if(x == BOARD_RGHT+1) {
13258                     if(y >= gameInfo.holdingsSize) break;
13259                     boards[0][y][BOARD_WIDTH-2] = 0;
13260                 } else break;
13261             }
13262             boards[0][y][x] = EmptySquare;
13263             DrawPosition(FALSE, boards[0]);
13264         }
13265         break;
13266
13267       case PromotePiece:
13268         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13269            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13270             selection = (ChessSquare) (PROMOTED piece);
13271         } else if(piece == EmptySquare) selection = WhiteSilver;
13272         else selection = (ChessSquare)((int)piece - 1);
13273         goto defaultlabel;
13274
13275       case DemotePiece:
13276         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13277            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13278             selection = (ChessSquare) (DEMOTED piece);
13279         } else if(piece == EmptySquare) selection = BlackSilver;
13280         else selection = (ChessSquare)((int)piece + 1);
13281         goto defaultlabel;
13282
13283       case WhiteQueen:
13284       case BlackQueen:
13285         if(gameInfo.variant == VariantShatranj ||
13286            gameInfo.variant == VariantXiangqi  ||
13287            gameInfo.variant == VariantCourier  ||
13288            gameInfo.variant == VariantMakruk     )
13289             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13290         goto defaultlabel;
13291
13292       case WhiteKing:
13293       case BlackKing:
13294         if(gameInfo.variant == VariantXiangqi)
13295             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13296         if(gameInfo.variant == VariantKnightmate)
13297             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13298       default:
13299         defaultlabel:
13300         if (gameMode == IcsExamining) {
13301             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13302             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13303                      PieceToChar(selection), AAA + x, ONE + y);
13304             SendToICS(buf);
13305         } else {
13306             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13307                 int n;
13308                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13309                     n = PieceToNumber(selection - BlackPawn);
13310                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13311                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13312                     boards[0][BOARD_HEIGHT-1-n][1]++;
13313                 } else
13314                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13315                     n = PieceToNumber(selection);
13316                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13317                     boards[0][n][BOARD_WIDTH-1] = selection;
13318                     boards[0][n][BOARD_WIDTH-2]++;
13319                 }
13320             } else
13321             boards[0][y][x] = selection;
13322             DrawPosition(TRUE, boards[0]);
13323         }
13324         break;
13325     }
13326 }
13327
13328
13329 void
13330 DropMenuEvent(selection, x, y)
13331      ChessSquare selection;
13332      int x, y;
13333 {
13334     ChessMove moveType;
13335
13336     switch (gameMode) {
13337       case IcsPlayingWhite:
13338       case MachinePlaysBlack:
13339         if (!WhiteOnMove(currentMove)) {
13340             DisplayMoveError(_("It is Black's turn"));
13341             return;
13342         }
13343         moveType = WhiteDrop;
13344         break;
13345       case IcsPlayingBlack:
13346       case MachinePlaysWhite:
13347         if (WhiteOnMove(currentMove)) {
13348             DisplayMoveError(_("It is White's turn"));
13349             return;
13350         }
13351         moveType = BlackDrop;
13352         break;
13353       case EditGame:
13354         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13355         break;
13356       default:
13357         return;
13358     }
13359
13360     if (moveType == BlackDrop && selection < BlackPawn) {
13361       selection = (ChessSquare) ((int) selection
13362                                  + (int) BlackPawn - (int) WhitePawn);
13363     }
13364     if (boards[currentMove][y][x] != EmptySquare) {
13365         DisplayMoveError(_("That square is occupied"));
13366         return;
13367     }
13368
13369     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13370 }
13371
13372 void
13373 AcceptEvent()
13374 {
13375     /* Accept a pending offer of any kind from opponent */
13376
13377     if (appData.icsActive) {
13378         SendToICS(ics_prefix);
13379         SendToICS("accept\n");
13380     } else if (cmailMsgLoaded) {
13381         if (currentMove == cmailOldMove &&
13382             commentList[cmailOldMove] != NULL &&
13383             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13384                    "Black offers a draw" : "White offers a draw")) {
13385             TruncateGame();
13386             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13387             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13388         } else {
13389             DisplayError(_("There is no pending offer on this move"), 0);
13390             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13391         }
13392     } else {
13393         /* Not used for offers from chess program */
13394     }
13395 }
13396
13397 void
13398 DeclineEvent()
13399 {
13400     /* Decline a pending offer of any kind from opponent */
13401
13402     if (appData.icsActive) {
13403         SendToICS(ics_prefix);
13404         SendToICS("decline\n");
13405     } else if (cmailMsgLoaded) {
13406         if (currentMove == cmailOldMove &&
13407             commentList[cmailOldMove] != NULL &&
13408             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13409                    "Black offers a draw" : "White offers a draw")) {
13410 #ifdef NOTDEF
13411             AppendComment(cmailOldMove, "Draw declined", TRUE);
13412             DisplayComment(cmailOldMove - 1, "Draw declined");
13413 #endif /*NOTDEF*/
13414         } else {
13415             DisplayError(_("There is no pending offer on this move"), 0);
13416         }
13417     } else {
13418         /* Not used for offers from chess program */
13419     }
13420 }
13421
13422 void
13423 RematchEvent()
13424 {
13425     /* Issue ICS rematch command */
13426     if (appData.icsActive) {
13427         SendToICS(ics_prefix);
13428         SendToICS("rematch\n");
13429     }
13430 }
13431
13432 void
13433 CallFlagEvent()
13434 {
13435     /* Call your opponent's flag (claim a win on time) */
13436     if (appData.icsActive) {
13437         SendToICS(ics_prefix);
13438         SendToICS("flag\n");
13439     } else {
13440         switch (gameMode) {
13441           default:
13442             return;
13443           case MachinePlaysWhite:
13444             if (whiteFlag) {
13445                 if (blackFlag)
13446                   GameEnds(GameIsDrawn, "Both players ran out of time",
13447                            GE_PLAYER);
13448                 else
13449                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13450             } else {
13451                 DisplayError(_("Your opponent is not out of time"), 0);
13452             }
13453             break;
13454           case MachinePlaysBlack:
13455             if (blackFlag) {
13456                 if (whiteFlag)
13457                   GameEnds(GameIsDrawn, "Both players ran out of time",
13458                            GE_PLAYER);
13459                 else
13460                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13461             } else {
13462                 DisplayError(_("Your opponent is not out of time"), 0);
13463             }
13464             break;
13465         }
13466     }
13467 }
13468
13469 void
13470 ClockClick(int which)
13471 {       // [HGM] code moved to back-end from winboard.c
13472         if(which) { // black clock
13473           if (gameMode == EditPosition || gameMode == IcsExamining) {
13474             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13475             SetBlackToPlayEvent();
13476           } else if (gameMode == EditGame || shiftKey) {
13477             AdjustClock(which, -1);
13478           } else if (gameMode == IcsPlayingWhite ||
13479                      gameMode == MachinePlaysBlack) {
13480             CallFlagEvent();
13481           }
13482         } else { // white clock
13483           if (gameMode == EditPosition || gameMode == IcsExamining) {
13484             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13485             SetWhiteToPlayEvent();
13486           } else if (gameMode == EditGame || shiftKey) {
13487             AdjustClock(which, -1);
13488           } else if (gameMode == IcsPlayingBlack ||
13489                    gameMode == MachinePlaysWhite) {
13490             CallFlagEvent();
13491           }
13492         }
13493 }
13494
13495 void
13496 DrawEvent()
13497 {
13498     /* Offer draw or accept pending draw offer from opponent */
13499
13500     if (appData.icsActive) {
13501         /* Note: tournament rules require draw offers to be
13502            made after you make your move but before you punch
13503            your clock.  Currently ICS doesn't let you do that;
13504            instead, you immediately punch your clock after making
13505            a move, but you can offer a draw at any time. */
13506
13507         SendToICS(ics_prefix);
13508         SendToICS("draw\n");
13509         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13510     } else if (cmailMsgLoaded) {
13511         if (currentMove == cmailOldMove &&
13512             commentList[cmailOldMove] != NULL &&
13513             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13514                    "Black offers a draw" : "White offers a draw")) {
13515             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13516             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13517         } else if (currentMove == cmailOldMove + 1) {
13518             char *offer = WhiteOnMove(cmailOldMove) ?
13519               "White offers a draw" : "Black offers a draw";
13520             AppendComment(currentMove, offer, TRUE);
13521             DisplayComment(currentMove - 1, offer);
13522             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13523         } else {
13524             DisplayError(_("You must make your move before offering a draw"), 0);
13525             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13526         }
13527     } else if (first.offeredDraw) {
13528         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13529     } else {
13530         if (first.sendDrawOffers) {
13531             SendToProgram("draw\n", &first);
13532             userOfferedDraw = TRUE;
13533         }
13534     }
13535 }
13536
13537 void
13538 AdjournEvent()
13539 {
13540     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13541
13542     if (appData.icsActive) {
13543         SendToICS(ics_prefix);
13544         SendToICS("adjourn\n");
13545     } else {
13546         /* Currently GNU Chess doesn't offer or accept Adjourns */
13547     }
13548 }
13549
13550
13551 void
13552 AbortEvent()
13553 {
13554     /* Offer Abort or accept pending Abort offer from opponent */
13555
13556     if (appData.icsActive) {
13557         SendToICS(ics_prefix);
13558         SendToICS("abort\n");
13559     } else {
13560         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13561     }
13562 }
13563
13564 void
13565 ResignEvent()
13566 {
13567     /* Resign.  You can do this even if it's not your turn. */
13568
13569     if (appData.icsActive) {
13570         SendToICS(ics_prefix);
13571         SendToICS("resign\n");
13572     } else {
13573         switch (gameMode) {
13574           case MachinePlaysWhite:
13575             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13576             break;
13577           case MachinePlaysBlack:
13578             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13579             break;
13580           case EditGame:
13581             if (cmailMsgLoaded) {
13582                 TruncateGame();
13583                 if (WhiteOnMove(cmailOldMove)) {
13584                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13585                 } else {
13586                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13587                 }
13588                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13589             }
13590             break;
13591           default:
13592             break;
13593         }
13594     }
13595 }
13596
13597
13598 void
13599 StopObservingEvent()
13600 {
13601     /* Stop observing current games */
13602     SendToICS(ics_prefix);
13603     SendToICS("unobserve\n");
13604 }
13605
13606 void
13607 StopExaminingEvent()
13608 {
13609     /* Stop observing current game */
13610     SendToICS(ics_prefix);
13611     SendToICS("unexamine\n");
13612 }
13613
13614 void
13615 ForwardInner(target)
13616      int target;
13617 {
13618     int limit;
13619
13620     if (appData.debugMode)
13621         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13622                 target, currentMove, forwardMostMove);
13623
13624     if (gameMode == EditPosition)
13625       return;
13626
13627     if (gameMode == PlayFromGameFile && !pausing)
13628       PauseEvent();
13629
13630     if (gameMode == IcsExamining && pausing)
13631       limit = pauseExamForwardMostMove;
13632     else
13633       limit = forwardMostMove;
13634
13635     if (target > limit) target = limit;
13636
13637     if (target > 0 && moveList[target - 1][0]) {
13638         int fromX, fromY, toX, toY;
13639         toX = moveList[target - 1][2] - AAA;
13640         toY = moveList[target - 1][3] - ONE;
13641         if (moveList[target - 1][1] == '@') {
13642             if (appData.highlightLastMove) {
13643                 SetHighlights(-1, -1, toX, toY);
13644             }
13645         } else {
13646             fromX = moveList[target - 1][0] - AAA;
13647             fromY = moveList[target - 1][1] - ONE;
13648             if (target == currentMove + 1) {
13649                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13650             }
13651             if (appData.highlightLastMove) {
13652                 SetHighlights(fromX, fromY, toX, toY);
13653             }
13654         }
13655     }
13656     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13657         gameMode == Training || gameMode == PlayFromGameFile ||
13658         gameMode == AnalyzeFile) {
13659         while (currentMove < target) {
13660             SendMoveToProgram(currentMove++, &first);
13661         }
13662     } else {
13663         currentMove = target;
13664     }
13665
13666     if (gameMode == EditGame || gameMode == EndOfGame) {
13667         whiteTimeRemaining = timeRemaining[0][currentMove];
13668         blackTimeRemaining = timeRemaining[1][currentMove];
13669     }
13670     DisplayBothClocks();
13671     DisplayMove(currentMove - 1);
13672     DrawPosition(FALSE, boards[currentMove]);
13673     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13674     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13675         DisplayComment(currentMove - 1, commentList[currentMove]);
13676     }
13677     DisplayBook(currentMove);
13678 }
13679
13680
13681 void
13682 ForwardEvent()
13683 {
13684     if (gameMode == IcsExamining && !pausing) {
13685         SendToICS(ics_prefix);
13686         SendToICS("forward\n");
13687     } else {
13688         ForwardInner(currentMove + 1);
13689     }
13690 }
13691
13692 void
13693 ToEndEvent()
13694 {
13695     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13696         /* to optimze, we temporarily turn off analysis mode while we feed
13697          * the remaining moves to the engine. Otherwise we get analysis output
13698          * after each move.
13699          */
13700         if (first.analysisSupport) {
13701           SendToProgram("exit\nforce\n", &first);
13702           first.analyzing = FALSE;
13703         }
13704     }
13705
13706     if (gameMode == IcsExamining && !pausing) {
13707         SendToICS(ics_prefix);
13708         SendToICS("forward 999999\n");
13709     } else {
13710         ForwardInner(forwardMostMove);
13711     }
13712
13713     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13714         /* we have fed all the moves, so reactivate analysis mode */
13715         SendToProgram("analyze\n", &first);
13716         first.analyzing = TRUE;
13717         /*first.maybeThinking = TRUE;*/
13718         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13719     }
13720 }
13721
13722 void
13723 BackwardInner(target)
13724      int target;
13725 {
13726     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13727
13728     if (appData.debugMode)
13729         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13730                 target, currentMove, forwardMostMove);
13731
13732     if (gameMode == EditPosition) return;
13733     if (currentMove <= backwardMostMove) {
13734         ClearHighlights();
13735         DrawPosition(full_redraw, boards[currentMove]);
13736         return;
13737     }
13738     if (gameMode == PlayFromGameFile && !pausing)
13739       PauseEvent();
13740
13741     if (moveList[target][0]) {
13742         int fromX, fromY, toX, toY;
13743         toX = moveList[target][2] - AAA;
13744         toY = moveList[target][3] - ONE;
13745         if (moveList[target][1] == '@') {
13746             if (appData.highlightLastMove) {
13747                 SetHighlights(-1, -1, toX, toY);
13748             }
13749         } else {
13750             fromX = moveList[target][0] - AAA;
13751             fromY = moveList[target][1] - ONE;
13752             if (target == currentMove - 1) {
13753                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13754             }
13755             if (appData.highlightLastMove) {
13756                 SetHighlights(fromX, fromY, toX, toY);
13757             }
13758         }
13759     }
13760     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13761         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13762         while (currentMove > target) {
13763             SendToProgram("undo\n", &first);
13764             currentMove--;
13765         }
13766     } else {
13767         currentMove = target;
13768     }
13769
13770     if (gameMode == EditGame || gameMode == EndOfGame) {
13771         whiteTimeRemaining = timeRemaining[0][currentMove];
13772         blackTimeRemaining = timeRemaining[1][currentMove];
13773     }
13774     DisplayBothClocks();
13775     DisplayMove(currentMove - 1);
13776     DrawPosition(full_redraw, boards[currentMove]);
13777     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13778     // [HGM] PV info: routine tests if comment empty
13779     DisplayComment(currentMove - 1, commentList[currentMove]);
13780     DisplayBook(currentMove);
13781 }
13782
13783 void
13784 BackwardEvent()
13785 {
13786     if (gameMode == IcsExamining && !pausing) {
13787         SendToICS(ics_prefix);
13788         SendToICS("backward\n");
13789     } else {
13790         BackwardInner(currentMove - 1);
13791     }
13792 }
13793
13794 void
13795 ToStartEvent()
13796 {
13797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13798         /* to optimize, we temporarily turn off analysis mode while we undo
13799          * all the moves. Otherwise we get analysis output after each undo.
13800          */
13801         if (first.analysisSupport) {
13802           SendToProgram("exit\nforce\n", &first);
13803           first.analyzing = FALSE;
13804         }
13805     }
13806
13807     if (gameMode == IcsExamining && !pausing) {
13808         SendToICS(ics_prefix);
13809         SendToICS("backward 999999\n");
13810     } else {
13811         BackwardInner(backwardMostMove);
13812     }
13813
13814     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13815         /* we have fed all the moves, so reactivate analysis mode */
13816         SendToProgram("analyze\n", &first);
13817         first.analyzing = TRUE;
13818         /*first.maybeThinking = TRUE;*/
13819         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13820     }
13821 }
13822
13823 void
13824 ToNrEvent(int to)
13825 {
13826   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13827   if (to >= forwardMostMove) to = forwardMostMove;
13828   if (to <= backwardMostMove) to = backwardMostMove;
13829   if (to < currentMove) {
13830     BackwardInner(to);
13831   } else {
13832     ForwardInner(to);
13833   }
13834 }
13835
13836 void
13837 RevertEvent(Boolean annotate)
13838 {
13839     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13840         return;
13841     }
13842     if (gameMode != IcsExamining) {
13843         DisplayError(_("You are not examining a game"), 0);
13844         return;
13845     }
13846     if (pausing) {
13847         DisplayError(_("You can't revert while pausing"), 0);
13848         return;
13849     }
13850     SendToICS(ics_prefix);
13851     SendToICS("revert\n");
13852 }
13853
13854 void
13855 RetractMoveEvent()
13856 {
13857     switch (gameMode) {
13858       case MachinePlaysWhite:
13859       case MachinePlaysBlack:
13860         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13861             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13862             return;
13863         }
13864         if (forwardMostMove < 2) return;
13865         currentMove = forwardMostMove = forwardMostMove - 2;
13866         whiteTimeRemaining = timeRemaining[0][currentMove];
13867         blackTimeRemaining = timeRemaining[1][currentMove];
13868         DisplayBothClocks();
13869         DisplayMove(currentMove - 1);
13870         ClearHighlights();/*!! could figure this out*/
13871         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13872         SendToProgram("remove\n", &first);
13873         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13874         break;
13875
13876       case BeginningOfGame:
13877       default:
13878         break;
13879
13880       case IcsPlayingWhite:
13881       case IcsPlayingBlack:
13882         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13883             SendToICS(ics_prefix);
13884             SendToICS("takeback 2\n");
13885         } else {
13886             SendToICS(ics_prefix);
13887             SendToICS("takeback 1\n");
13888         }
13889         break;
13890     }
13891 }
13892
13893 void
13894 MoveNowEvent()
13895 {
13896     ChessProgramState *cps;
13897
13898     switch (gameMode) {
13899       case MachinePlaysWhite:
13900         if (!WhiteOnMove(forwardMostMove)) {
13901             DisplayError(_("It is your turn"), 0);
13902             return;
13903         }
13904         cps = &first;
13905         break;
13906       case MachinePlaysBlack:
13907         if (WhiteOnMove(forwardMostMove)) {
13908             DisplayError(_("It is your turn"), 0);
13909             return;
13910         }
13911         cps = &first;
13912         break;
13913       case TwoMachinesPlay:
13914         if (WhiteOnMove(forwardMostMove) ==
13915             (first.twoMachinesColor[0] == 'w')) {
13916             cps = &first;
13917         } else {
13918             cps = &second;
13919         }
13920         break;
13921       case BeginningOfGame:
13922       default:
13923         return;
13924     }
13925     SendToProgram("?\n", cps);
13926 }
13927
13928 void
13929 TruncateGameEvent()
13930 {
13931     EditGameEvent();
13932     if (gameMode != EditGame) return;
13933     TruncateGame();
13934 }
13935
13936 void
13937 TruncateGame()
13938 {
13939     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13940     if (forwardMostMove > currentMove) {
13941         if (gameInfo.resultDetails != NULL) {
13942             free(gameInfo.resultDetails);
13943             gameInfo.resultDetails = NULL;
13944             gameInfo.result = GameUnfinished;
13945         }
13946         forwardMostMove = currentMove;
13947         HistorySet(parseList, backwardMostMove, forwardMostMove,
13948                    currentMove-1);
13949     }
13950 }
13951
13952 void
13953 HintEvent()
13954 {
13955     if (appData.noChessProgram) return;
13956     switch (gameMode) {
13957       case MachinePlaysWhite:
13958         if (WhiteOnMove(forwardMostMove)) {
13959             DisplayError(_("Wait until your turn"), 0);
13960             return;
13961         }
13962         break;
13963       case BeginningOfGame:
13964       case MachinePlaysBlack:
13965         if (!WhiteOnMove(forwardMostMove)) {
13966             DisplayError(_("Wait until your turn"), 0);
13967             return;
13968         }
13969         break;
13970       default:
13971         DisplayError(_("No hint available"), 0);
13972         return;
13973     }
13974     SendToProgram("hint\n", &first);
13975     hintRequested = TRUE;
13976 }
13977
13978 void
13979 BookEvent()
13980 {
13981     if (appData.noChessProgram) return;
13982     switch (gameMode) {
13983       case MachinePlaysWhite:
13984         if (WhiteOnMove(forwardMostMove)) {
13985             DisplayError(_("Wait until your turn"), 0);
13986             return;
13987         }
13988         break;
13989       case BeginningOfGame:
13990       case MachinePlaysBlack:
13991         if (!WhiteOnMove(forwardMostMove)) {
13992             DisplayError(_("Wait until your turn"), 0);
13993             return;
13994         }
13995         break;
13996       case EditPosition:
13997         EditPositionDone(TRUE);
13998         break;
13999       case TwoMachinesPlay:
14000         return;
14001       default:
14002         break;
14003     }
14004     SendToProgram("bk\n", &first);
14005     bookOutput[0] = NULLCHAR;
14006     bookRequested = TRUE;
14007 }
14008
14009 void
14010 AboutGameEvent()
14011 {
14012     char *tags = PGNTags(&gameInfo);
14013     TagsPopUp(tags, CmailMsg());
14014     free(tags);
14015 }
14016
14017 /* end button procedures */
14018
14019 void
14020 PrintPosition(fp, move)
14021      FILE *fp;
14022      int move;
14023 {
14024     int i, j;
14025
14026     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14027         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14028             char c = PieceToChar(boards[move][i][j]);
14029             fputc(c == 'x' ? '.' : c, fp);
14030             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14031         }
14032     }
14033     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14034       fprintf(fp, "white to play\n");
14035     else
14036       fprintf(fp, "black to play\n");
14037 }
14038
14039 void
14040 PrintOpponents(fp)
14041      FILE *fp;
14042 {
14043     if (gameInfo.white != NULL) {
14044         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14045     } else {
14046         fprintf(fp, "\n");
14047     }
14048 }
14049
14050 /* Find last component of program's own name, using some heuristics */
14051 void
14052 TidyProgramName(prog, host, buf)
14053      char *prog, *host, buf[MSG_SIZ];
14054 {
14055     char *p, *q;
14056     int local = (strcmp(host, "localhost") == 0);
14057     while (!local && (p = strchr(prog, ';')) != NULL) {
14058         p++;
14059         while (*p == ' ') p++;
14060         prog = p;
14061     }
14062     if (*prog == '"' || *prog == '\'') {
14063         q = strchr(prog + 1, *prog);
14064     } else {
14065         q = strchr(prog, ' ');
14066     }
14067     if (q == NULL) q = prog + strlen(prog);
14068     p = q;
14069     while (p >= prog && *p != '/' && *p != '\\') p--;
14070     p++;
14071     if(p == prog && *p == '"') p++;
14072     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14073     memcpy(buf, p, q - p);
14074     buf[q - p] = NULLCHAR;
14075     if (!local) {
14076         strcat(buf, "@");
14077         strcat(buf, host);
14078     }
14079 }
14080
14081 char *
14082 TimeControlTagValue()
14083 {
14084     char buf[MSG_SIZ];
14085     if (!appData.clockMode) {
14086       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14087     } else if (movesPerSession > 0) {
14088       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14089     } else if (timeIncrement == 0) {
14090       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14091     } else {
14092       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14093     }
14094     return StrSave(buf);
14095 }
14096
14097 void
14098 SetGameInfo()
14099 {
14100     /* This routine is used only for certain modes */
14101     VariantClass v = gameInfo.variant;
14102     ChessMove r = GameUnfinished;
14103     char *p = NULL;
14104
14105     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14106         r = gameInfo.result;
14107         p = gameInfo.resultDetails;
14108         gameInfo.resultDetails = NULL;
14109     }
14110     ClearGameInfo(&gameInfo);
14111     gameInfo.variant = v;
14112
14113     switch (gameMode) {
14114       case MachinePlaysWhite:
14115         gameInfo.event = StrSave( appData.pgnEventHeader );
14116         gameInfo.site = StrSave(HostName());
14117         gameInfo.date = PGNDate();
14118         gameInfo.round = StrSave("-");
14119         gameInfo.white = StrSave(first.tidy);
14120         gameInfo.black = StrSave(UserName());
14121         gameInfo.timeControl = TimeControlTagValue();
14122         break;
14123
14124       case MachinePlaysBlack:
14125         gameInfo.event = StrSave( appData.pgnEventHeader );
14126         gameInfo.site = StrSave(HostName());
14127         gameInfo.date = PGNDate();
14128         gameInfo.round = StrSave("-");
14129         gameInfo.white = StrSave(UserName());
14130         gameInfo.black = StrSave(first.tidy);
14131         gameInfo.timeControl = TimeControlTagValue();
14132         break;
14133
14134       case TwoMachinesPlay:
14135         gameInfo.event = StrSave( appData.pgnEventHeader );
14136         gameInfo.site = StrSave(HostName());
14137         gameInfo.date = PGNDate();
14138         if (roundNr > 0) {
14139             char buf[MSG_SIZ];
14140             snprintf(buf, MSG_SIZ, "%d", roundNr);
14141             gameInfo.round = StrSave(buf);
14142         } else {
14143             gameInfo.round = StrSave("-");
14144         }
14145         if (first.twoMachinesColor[0] == 'w') {
14146             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14147             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14148         } else {
14149             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14150             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14151         }
14152         gameInfo.timeControl = TimeControlTagValue();
14153         break;
14154
14155       case EditGame:
14156         gameInfo.event = StrSave("Edited game");
14157         gameInfo.site = StrSave(HostName());
14158         gameInfo.date = PGNDate();
14159         gameInfo.round = StrSave("-");
14160         gameInfo.white = StrSave("-");
14161         gameInfo.black = StrSave("-");
14162         gameInfo.result = r;
14163         gameInfo.resultDetails = p;
14164         break;
14165
14166       case EditPosition:
14167         gameInfo.event = StrSave("Edited position");
14168         gameInfo.site = StrSave(HostName());
14169         gameInfo.date = PGNDate();
14170         gameInfo.round = StrSave("-");
14171         gameInfo.white = StrSave("-");
14172         gameInfo.black = StrSave("-");
14173         break;
14174
14175       case IcsPlayingWhite:
14176       case IcsPlayingBlack:
14177       case IcsObserving:
14178       case IcsExamining:
14179         break;
14180
14181       case PlayFromGameFile:
14182         gameInfo.event = StrSave("Game from non-PGN file");
14183         gameInfo.site = StrSave(HostName());
14184         gameInfo.date = PGNDate();
14185         gameInfo.round = StrSave("-");
14186         gameInfo.white = StrSave("?");
14187         gameInfo.black = StrSave("?");
14188         break;
14189
14190       default:
14191         break;
14192     }
14193 }
14194
14195 void
14196 ReplaceComment(index, text)
14197      int index;
14198      char *text;
14199 {
14200     int len;
14201     char *p;
14202     float score;
14203
14204     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14205        pvInfoList[index-1].depth == len &&
14206        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14207        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14208     while (*text == '\n') text++;
14209     len = strlen(text);
14210     while (len > 0 && text[len - 1] == '\n') len--;
14211
14212     if (commentList[index] != NULL)
14213       free(commentList[index]);
14214
14215     if (len == 0) {
14216         commentList[index] = NULL;
14217         return;
14218     }
14219   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14220       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14221       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14222     commentList[index] = (char *) malloc(len + 2);
14223     strncpy(commentList[index], text, len);
14224     commentList[index][len] = '\n';
14225     commentList[index][len + 1] = NULLCHAR;
14226   } else {
14227     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14228     char *p;
14229     commentList[index] = (char *) malloc(len + 7);
14230     safeStrCpy(commentList[index], "{\n", 3);
14231     safeStrCpy(commentList[index]+2, text, len+1);
14232     commentList[index][len+2] = NULLCHAR;
14233     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14234     strcat(commentList[index], "\n}\n");
14235   }
14236 }
14237
14238 void
14239 CrushCRs(text)
14240      char *text;
14241 {
14242   char *p = text;
14243   char *q = text;
14244   char ch;
14245
14246   do {
14247     ch = *p++;
14248     if (ch == '\r') continue;
14249     *q++ = ch;
14250   } while (ch != '\0');
14251 }
14252
14253 void
14254 AppendComment(index, text, addBraces)
14255      int index;
14256      char *text;
14257      Boolean addBraces; // [HGM] braces: tells if we should add {}
14258 {
14259     int oldlen, len;
14260     char *old;
14261
14262 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14263     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14264
14265     CrushCRs(text);
14266     while (*text == '\n') text++;
14267     len = strlen(text);
14268     while (len > 0 && text[len - 1] == '\n') len--;
14269
14270     if (len == 0) return;
14271
14272     if (commentList[index] != NULL) {
14273         old = commentList[index];
14274         oldlen = strlen(old);
14275         while(commentList[index][oldlen-1] ==  '\n')
14276           commentList[index][--oldlen] = NULLCHAR;
14277         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14278         safeStrCpy(commentList[index], old, oldlen + len + 6);
14279         free(old);
14280         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14281         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14282           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14283           while (*text == '\n') { text++; len--; }
14284           commentList[index][--oldlen] = NULLCHAR;
14285       }
14286         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14287         else          strcat(commentList[index], "\n");
14288         strcat(commentList[index], text);
14289         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14290         else          strcat(commentList[index], "\n");
14291     } else {
14292         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14293         if(addBraces)
14294           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14295         else commentList[index][0] = NULLCHAR;
14296         strcat(commentList[index], text);
14297         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14298         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14299     }
14300 }
14301
14302 static char * FindStr( char * text, char * sub_text )
14303 {
14304     char * result = strstr( text, sub_text );
14305
14306     if( result != NULL ) {
14307         result += strlen( sub_text );
14308     }
14309
14310     return result;
14311 }
14312
14313 /* [AS] Try to extract PV info from PGN comment */
14314 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14315 char *GetInfoFromComment( int index, char * text )
14316 {
14317     char * sep = text, *p;
14318
14319     if( text != NULL && index > 0 ) {
14320         int score = 0;
14321         int depth = 0;
14322         int time = -1, sec = 0, deci;
14323         char * s_eval = FindStr( text, "[%eval " );
14324         char * s_emt = FindStr( text, "[%emt " );
14325
14326         if( s_eval != NULL || s_emt != NULL ) {
14327             /* New style */
14328             char delim;
14329
14330             if( s_eval != NULL ) {
14331                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14332                     return text;
14333                 }
14334
14335                 if( delim != ']' ) {
14336                     return text;
14337                 }
14338             }
14339
14340             if( s_emt != NULL ) {
14341             }
14342                 return text;
14343         }
14344         else {
14345             /* We expect something like: [+|-]nnn.nn/dd */
14346             int score_lo = 0;
14347
14348             if(*text != '{') return text; // [HGM] braces: must be normal comment
14349
14350             sep = strchr( text, '/' );
14351             if( sep == NULL || sep < (text+4) ) {
14352                 return text;
14353             }
14354
14355             p = text;
14356             if(p[1] == '(') { // comment starts with PV
14357                p = strchr(p, ')'); // locate end of PV
14358                if(p == NULL || sep < p+5) return text;
14359                // at this point we have something like "{(.*) +0.23/6 ..."
14360                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14361                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14362                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14363             }
14364             time = -1; sec = -1; deci = -1;
14365             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14366                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14367                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14368                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14369                 return text;
14370             }
14371
14372             if( score_lo < 0 || score_lo >= 100 ) {
14373                 return text;
14374             }
14375
14376             if(sec >= 0) time = 600*time + 10*sec; else
14377             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14378
14379             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14380
14381             /* [HGM] PV time: now locate end of PV info */
14382             while( *++sep >= '0' && *sep <= '9'); // strip depth
14383             if(time >= 0)
14384             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14385             if(sec >= 0)
14386             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14387             if(deci >= 0)
14388             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14389             while(*sep == ' ') sep++;
14390         }
14391
14392         if( depth <= 0 ) {
14393             return text;
14394         }
14395
14396         if( time < 0 ) {
14397             time = -1;
14398         }
14399
14400         pvInfoList[index-1].depth = depth;
14401         pvInfoList[index-1].score = score;
14402         pvInfoList[index-1].time  = 10*time; // centi-sec
14403         if(*sep == '}') *sep = 0; else *--sep = '{';
14404         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14405     }
14406     return sep;
14407 }
14408
14409 void
14410 SendToProgram(message, cps)
14411      char *message;
14412      ChessProgramState *cps;
14413 {
14414     int count, outCount, error;
14415     char buf[MSG_SIZ];
14416
14417     if (cps->pr == NULL) return;
14418     Attention(cps);
14419
14420     if (appData.debugMode) {
14421         TimeMark now;
14422         GetTimeMark(&now);
14423         fprintf(debugFP, "%ld >%-6s: %s",
14424                 SubtractTimeMarks(&now, &programStartTime),
14425                 cps->which, message);
14426     }
14427
14428     count = strlen(message);
14429     outCount = OutputToProcess(cps->pr, message, count, &error);
14430     if (outCount < count && !exiting
14431                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14432       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14433       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14434         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14435             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14436                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14437                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14438                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14439             } else {
14440                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14441                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14442                 gameInfo.result = res;
14443             }
14444             gameInfo.resultDetails = StrSave(buf);
14445         }
14446         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14447         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14448     }
14449 }
14450
14451 void
14452 ReceiveFromProgram(isr, closure, message, count, error)
14453      InputSourceRef isr;
14454      VOIDSTAR closure;
14455      char *message;
14456      int count;
14457      int error;
14458 {
14459     char *end_str;
14460     char buf[MSG_SIZ];
14461     ChessProgramState *cps = (ChessProgramState *)closure;
14462
14463     if (isr != cps->isr) return; /* Killed intentionally */
14464     if (count <= 0) {
14465         if (count == 0) {
14466             RemoveInputSource(cps->isr);
14467             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14468             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14469                     _(cps->which), cps->program);
14470         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14471                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14472                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14473                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14474                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14475                 } else {
14476                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14477                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14478                     gameInfo.result = res;
14479                 }
14480                 gameInfo.resultDetails = StrSave(buf);
14481             }
14482             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14483             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14484         } else {
14485             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14486                     _(cps->which), cps->program);
14487             RemoveInputSource(cps->isr);
14488
14489             /* [AS] Program is misbehaving badly... kill it */
14490             if( count == -2 ) {
14491                 DestroyChildProcess( cps->pr, 9 );
14492                 cps->pr = NoProc;
14493             }
14494
14495             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14496         }
14497         return;
14498     }
14499
14500     if ((end_str = strchr(message, '\r')) != NULL)
14501       *end_str = NULLCHAR;
14502     if ((end_str = strchr(message, '\n')) != NULL)
14503       *end_str = NULLCHAR;
14504
14505     if (appData.debugMode) {
14506         TimeMark now; int print = 1;
14507         char *quote = ""; char c; int i;
14508
14509         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14510                 char start = message[0];
14511                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14512                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14513                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14514                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14515                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14516                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14517                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14518                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14519                    sscanf(message, "hint: %c", &c)!=1 && 
14520                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14521                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14522                     print = (appData.engineComments >= 2);
14523                 }
14524                 message[0] = start; // restore original message
14525         }
14526         if(print) {
14527                 GetTimeMark(&now);
14528                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14529                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14530                         quote,
14531                         message);
14532         }
14533     }
14534
14535     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14536     if (appData.icsEngineAnalyze) {
14537         if (strstr(message, "whisper") != NULL ||
14538              strstr(message, "kibitz") != NULL ||
14539             strstr(message, "tellics") != NULL) return;
14540     }
14541
14542     HandleMachineMove(message, cps);
14543 }
14544
14545
14546 void
14547 SendTimeControl(cps, mps, tc, inc, sd, st)
14548      ChessProgramState *cps;
14549      int mps, inc, sd, st;
14550      long tc;
14551 {
14552     char buf[MSG_SIZ];
14553     int seconds;
14554
14555     if( timeControl_2 > 0 ) {
14556         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14557             tc = timeControl_2;
14558         }
14559     }
14560     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14561     inc /= cps->timeOdds;
14562     st  /= cps->timeOdds;
14563
14564     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14565
14566     if (st > 0) {
14567       /* Set exact time per move, normally using st command */
14568       if (cps->stKludge) {
14569         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14570         seconds = st % 60;
14571         if (seconds == 0) {
14572           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14573         } else {
14574           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14575         }
14576       } else {
14577         snprintf(buf, MSG_SIZ, "st %d\n", st);
14578       }
14579     } else {
14580       /* Set conventional or incremental time control, using level command */
14581       if (seconds == 0) {
14582         /* Note old gnuchess bug -- minutes:seconds used to not work.
14583            Fixed in later versions, but still avoid :seconds
14584            when seconds is 0. */
14585         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14586       } else {
14587         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14588                  seconds, inc/1000.);
14589       }
14590     }
14591     SendToProgram(buf, cps);
14592
14593     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14594     /* Orthogonally, limit search to given depth */
14595     if (sd > 0) {
14596       if (cps->sdKludge) {
14597         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14598       } else {
14599         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14600       }
14601       SendToProgram(buf, cps);
14602     }
14603
14604     if(cps->nps >= 0) { /* [HGM] nps */
14605         if(cps->supportsNPS == FALSE)
14606           cps->nps = -1; // don't use if engine explicitly says not supported!
14607         else {
14608           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14609           SendToProgram(buf, cps);
14610         }
14611     }
14612 }
14613
14614 ChessProgramState *WhitePlayer()
14615 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14616 {
14617     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14618        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14619         return &second;
14620     return &first;
14621 }
14622
14623 void
14624 SendTimeRemaining(cps, machineWhite)
14625      ChessProgramState *cps;
14626      int /*boolean*/ machineWhite;
14627 {
14628     char message[MSG_SIZ];
14629     long time, otime;
14630
14631     /* Note: this routine must be called when the clocks are stopped
14632        or when they have *just* been set or switched; otherwise
14633        it will be off by the time since the current tick started.
14634     */
14635     if (machineWhite) {
14636         time = whiteTimeRemaining / 10;
14637         otime = blackTimeRemaining / 10;
14638     } else {
14639         time = blackTimeRemaining / 10;
14640         otime = whiteTimeRemaining / 10;
14641     }
14642     /* [HGM] translate opponent's time by time-odds factor */
14643     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14644     if (appData.debugMode) {
14645         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14646     }
14647
14648     if (time <= 0) time = 1;
14649     if (otime <= 0) otime = 1;
14650
14651     snprintf(message, MSG_SIZ, "time %ld\n", time);
14652     SendToProgram(message, cps);
14653
14654     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14655     SendToProgram(message, cps);
14656 }
14657
14658 int
14659 BoolFeature(p, name, loc, cps)
14660      char **p;
14661      char *name;
14662      int *loc;
14663      ChessProgramState *cps;
14664 {
14665   char buf[MSG_SIZ];
14666   int len = strlen(name);
14667   int val;
14668
14669   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14670     (*p) += len + 1;
14671     sscanf(*p, "%d", &val);
14672     *loc = (val != 0);
14673     while (**p && **p != ' ')
14674       (*p)++;
14675     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14676     SendToProgram(buf, cps);
14677     return TRUE;
14678   }
14679   return FALSE;
14680 }
14681
14682 int
14683 IntFeature(p, name, loc, cps)
14684      char **p;
14685      char *name;
14686      int *loc;
14687      ChessProgramState *cps;
14688 {
14689   char buf[MSG_SIZ];
14690   int len = strlen(name);
14691   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14692     (*p) += len + 1;
14693     sscanf(*p, "%d", loc);
14694     while (**p && **p != ' ') (*p)++;
14695     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14696     SendToProgram(buf, cps);
14697     return TRUE;
14698   }
14699   return FALSE;
14700 }
14701
14702 int
14703 StringFeature(p, name, loc, cps)
14704      char **p;
14705      char *name;
14706      char loc[];
14707      ChessProgramState *cps;
14708 {
14709   char buf[MSG_SIZ];
14710   int len = strlen(name);
14711   if (strncmp((*p), name, len) == 0
14712       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14713     (*p) += len + 2;
14714     sscanf(*p, "%[^\"]", loc);
14715     while (**p && **p != '\"') (*p)++;
14716     if (**p == '\"') (*p)++;
14717     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14718     SendToProgram(buf, cps);
14719     return TRUE;
14720   }
14721   return FALSE;
14722 }
14723
14724 int
14725 ParseOption(Option *opt, ChessProgramState *cps)
14726 // [HGM] options: process the string that defines an engine option, and determine
14727 // name, type, default value, and allowed value range
14728 {
14729         char *p, *q, buf[MSG_SIZ];
14730         int n, min = (-1)<<31, max = 1<<31, def;
14731
14732         if(p = strstr(opt->name, " -spin ")) {
14733             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14734             if(max < min) max = min; // enforce consistency
14735             if(def < min) def = min;
14736             if(def > max) def = max;
14737             opt->value = def;
14738             opt->min = min;
14739             opt->max = max;
14740             opt->type = Spin;
14741         } else if((p = strstr(opt->name, " -slider "))) {
14742             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14743             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14744             if(max < min) max = min; // enforce consistency
14745             if(def < min) def = min;
14746             if(def > max) def = max;
14747             opt->value = def;
14748             opt->min = min;
14749             opt->max = max;
14750             opt->type = Spin; // Slider;
14751         } else if((p = strstr(opt->name, " -string "))) {
14752             opt->textValue = p+9;
14753             opt->type = TextBox;
14754         } else if((p = strstr(opt->name, " -file "))) {
14755             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14756             opt->textValue = p+7;
14757             opt->type = FileName; // FileName;
14758         } else if((p = strstr(opt->name, " -path "))) {
14759             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14760             opt->textValue = p+7;
14761             opt->type = PathName; // PathName;
14762         } else if(p = strstr(opt->name, " -check ")) {
14763             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14764             opt->value = (def != 0);
14765             opt->type = CheckBox;
14766         } else if(p = strstr(opt->name, " -combo ")) {
14767             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14768             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14769             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14770             opt->value = n = 0;
14771             while(q = StrStr(q, " /// ")) {
14772                 n++; *q = 0;    // count choices, and null-terminate each of them
14773                 q += 5;
14774                 if(*q == '*') { // remember default, which is marked with * prefix
14775                     q++;
14776                     opt->value = n;
14777                 }
14778                 cps->comboList[cps->comboCnt++] = q;
14779             }
14780             cps->comboList[cps->comboCnt++] = NULL;
14781             opt->max = n + 1;
14782             opt->type = ComboBox;
14783         } else if(p = strstr(opt->name, " -button")) {
14784             opt->type = Button;
14785         } else if(p = strstr(opt->name, " -save")) {
14786             opt->type = SaveButton;
14787         } else return FALSE;
14788         *p = 0; // terminate option name
14789         // now look if the command-line options define a setting for this engine option.
14790         if(cps->optionSettings && cps->optionSettings[0])
14791             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14792         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14793           snprintf(buf, MSG_SIZ, "option %s", p);
14794                 if(p = strstr(buf, ",")) *p = 0;
14795                 if(q = strchr(buf, '=')) switch(opt->type) {
14796                     case ComboBox:
14797                         for(n=0; n<opt->max; n++)
14798                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14799                         break;
14800                     case TextBox:
14801                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14802                         break;
14803                     case Spin:
14804                     case CheckBox:
14805                         opt->value = atoi(q+1);
14806                     default:
14807                         break;
14808                 }
14809                 strcat(buf, "\n");
14810                 SendToProgram(buf, cps);
14811         }
14812         return TRUE;
14813 }
14814
14815 void
14816 FeatureDone(cps, val)
14817      ChessProgramState* cps;
14818      int val;
14819 {
14820   DelayedEventCallback cb = GetDelayedEvent();
14821   if ((cb == InitBackEnd3 && cps == &first) ||
14822       (cb == SettingsMenuIfReady && cps == &second) ||
14823       (cb == LoadEngine) ||
14824       (cb == TwoMachinesEventIfReady)) {
14825     CancelDelayedEvent();
14826     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14827   }
14828   cps->initDone = val;
14829 }
14830
14831 /* Parse feature command from engine */
14832 void
14833 ParseFeatures(args, cps)
14834      char* args;
14835      ChessProgramState *cps;
14836 {
14837   char *p = args;
14838   char *q;
14839   int val;
14840   char buf[MSG_SIZ];
14841
14842   for (;;) {
14843     while (*p == ' ') p++;
14844     if (*p == NULLCHAR) return;
14845
14846     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14847     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14848     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14849     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14850     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14851     if (BoolFeature(&p, "reuse", &val, cps)) {
14852       /* Engine can disable reuse, but can't enable it if user said no */
14853       if (!val) cps->reuse = FALSE;
14854       continue;
14855     }
14856     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14857     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14858       if (gameMode == TwoMachinesPlay) {
14859         DisplayTwoMachinesTitle();
14860       } else {
14861         DisplayTitle("");
14862       }
14863       continue;
14864     }
14865     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14866     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14867     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14868     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14869     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14870     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14871     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14872     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14873     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14874     if (IntFeature(&p, "done", &val, cps)) {
14875       FeatureDone(cps, val);
14876       continue;
14877     }
14878     /* Added by Tord: */
14879     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14880     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14881     /* End of additions by Tord */
14882
14883     /* [HGM] added features: */
14884     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14885     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14886     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14887     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14888     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14889     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14890     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14891         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14892           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14893             SendToProgram(buf, cps);
14894             continue;
14895         }
14896         if(cps->nrOptions >= MAX_OPTIONS) {
14897             cps->nrOptions--;
14898             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14899             DisplayError(buf, 0);
14900         }
14901         continue;
14902     }
14903     /* End of additions by HGM */
14904
14905     /* unknown feature: complain and skip */
14906     q = p;
14907     while (*q && *q != '=') q++;
14908     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14909     SendToProgram(buf, cps);
14910     p = q;
14911     if (*p == '=') {
14912       p++;
14913       if (*p == '\"') {
14914         p++;
14915         while (*p && *p != '\"') p++;
14916         if (*p == '\"') p++;
14917       } else {
14918         while (*p && *p != ' ') p++;
14919       }
14920     }
14921   }
14922
14923 }
14924
14925 void
14926 PeriodicUpdatesEvent(newState)
14927      int newState;
14928 {
14929     if (newState == appData.periodicUpdates)
14930       return;
14931
14932     appData.periodicUpdates=newState;
14933
14934     /* Display type changes, so update it now */
14935 //    DisplayAnalysis();
14936
14937     /* Get the ball rolling again... */
14938     if (newState) {
14939         AnalysisPeriodicEvent(1);
14940         StartAnalysisClock();
14941     }
14942 }
14943
14944 void
14945 PonderNextMoveEvent(newState)
14946      int newState;
14947 {
14948     if (newState == appData.ponderNextMove) return;
14949     if (gameMode == EditPosition) EditPositionDone(TRUE);
14950     if (newState) {
14951         SendToProgram("hard\n", &first);
14952         if (gameMode == TwoMachinesPlay) {
14953             SendToProgram("hard\n", &second);
14954         }
14955     } else {
14956         SendToProgram("easy\n", &first);
14957         thinkOutput[0] = NULLCHAR;
14958         if (gameMode == TwoMachinesPlay) {
14959             SendToProgram("easy\n", &second);
14960         }
14961     }
14962     appData.ponderNextMove = newState;
14963 }
14964
14965 void
14966 NewSettingEvent(option, feature, command, value)
14967      char *command;
14968      int option, value, *feature;
14969 {
14970     char buf[MSG_SIZ];
14971
14972     if (gameMode == EditPosition) EditPositionDone(TRUE);
14973     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14974     if(feature == NULL || *feature) SendToProgram(buf, &first);
14975     if (gameMode == TwoMachinesPlay) {
14976         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14977     }
14978 }
14979
14980 void
14981 ShowThinkingEvent()
14982 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14983 {
14984     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14985     int newState = appData.showThinking
14986         // [HGM] thinking: other features now need thinking output as well
14987         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14988
14989     if (oldState == newState) return;
14990     oldState = newState;
14991     if (gameMode == EditPosition) EditPositionDone(TRUE);
14992     if (oldState) {
14993         SendToProgram("post\n", &first);
14994         if (gameMode == TwoMachinesPlay) {
14995             SendToProgram("post\n", &second);
14996         }
14997     } else {
14998         SendToProgram("nopost\n", &first);
14999         thinkOutput[0] = NULLCHAR;
15000         if (gameMode == TwoMachinesPlay) {
15001             SendToProgram("nopost\n", &second);
15002         }
15003     }
15004 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15005 }
15006
15007 void
15008 AskQuestionEvent(title, question, replyPrefix, which)
15009      char *title; char *question; char *replyPrefix; char *which;
15010 {
15011   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15012   if (pr == NoProc) return;
15013   AskQuestion(title, question, replyPrefix, pr);
15014 }
15015
15016 void
15017 TypeInEvent(char firstChar)
15018 {
15019     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15020         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15021         gameMode == AnalyzeMode || gameMode == EditGame || \r
15022         gameMode == EditPosition || gameMode == IcsExamining ||\r
15023         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15024         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15025                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15026                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15027         gameMode == Training) PopUpMoveDialog(firstChar);
15028 }
15029
15030 void
15031 TypeInDoneEvent(char *move)
15032 {
15033         Board board;
15034         int n, fromX, fromY, toX, toY;
15035         char promoChar;
15036         ChessMove moveType;\r
15037
15038         // [HGM] FENedit\r
15039         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15040                 EditPositionPasteFEN(move);\r
15041                 return;\r
15042         }\r
15043         // [HGM] movenum: allow move number to be typed in any mode\r
15044         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15045           ToNrEvent(2*n-1);\r
15046           return;\r
15047         }\r
15048
15049       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15050         gameMode != Training) {\r
15051         DisplayMoveError(_("Displayed move is not current"));\r
15052       } else {\r
15053         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15054           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15055         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15056         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15057           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15058           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15059         } else {\r
15060           DisplayMoveError(_("Could not parse move"));\r
15061         }
15062       }\r
15063 }\r
15064
15065 void
15066 DisplayMove(moveNumber)
15067      int moveNumber;
15068 {
15069     char message[MSG_SIZ];
15070     char res[MSG_SIZ];
15071     char cpThinkOutput[MSG_SIZ];
15072
15073     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15074
15075     if (moveNumber == forwardMostMove - 1 ||
15076         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15077
15078         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15079
15080         if (strchr(cpThinkOutput, '\n')) {
15081             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15082         }
15083     } else {
15084         *cpThinkOutput = NULLCHAR;
15085     }
15086
15087     /* [AS] Hide thinking from human user */
15088     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15089         *cpThinkOutput = NULLCHAR;
15090         if( thinkOutput[0] != NULLCHAR ) {
15091             int i;
15092
15093             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15094                 cpThinkOutput[i] = '.';
15095             }
15096             cpThinkOutput[i] = NULLCHAR;
15097             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15098         }
15099     }
15100
15101     if (moveNumber == forwardMostMove - 1 &&
15102         gameInfo.resultDetails != NULL) {
15103         if (gameInfo.resultDetails[0] == NULLCHAR) {
15104           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15105         } else {
15106           snprintf(res, MSG_SIZ, " {%s} %s",
15107                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15108         }
15109     } else {
15110         res[0] = NULLCHAR;
15111     }
15112
15113     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15114         DisplayMessage(res, cpThinkOutput);
15115     } else {
15116       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15117                 WhiteOnMove(moveNumber) ? " " : ".. ",
15118                 parseList[moveNumber], res);
15119         DisplayMessage(message, cpThinkOutput);
15120     }
15121 }
15122
15123 void
15124 DisplayComment(moveNumber, text)
15125      int moveNumber;
15126      char *text;
15127 {
15128     char title[MSG_SIZ];
15129     char buf[8000]; // comment can be long!
15130     int score, depth;
15131
15132     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15133       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15134     } else {
15135       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15136               WhiteOnMove(moveNumber) ? " " : ".. ",
15137               parseList[moveNumber]);
15138     }
15139     // [HGM] PV info: display PV info together with (or as) comment
15140     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15141       if(text == NULL) text = "";
15142       score = pvInfoList[moveNumber].score;
15143       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15144               depth, (pvInfoList[moveNumber].time+50)/100, text);
15145       text = buf;
15146     }
15147     if (text != NULL && (appData.autoDisplayComment || commentUp))
15148         CommentPopUp(title, text);
15149 }
15150
15151 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15152  * might be busy thinking or pondering.  It can be omitted if your
15153  * gnuchess is configured to stop thinking immediately on any user
15154  * input.  However, that gnuchess feature depends on the FIONREAD
15155  * ioctl, which does not work properly on some flavors of Unix.
15156  */
15157 void
15158 Attention(cps)
15159      ChessProgramState *cps;
15160 {
15161 #if ATTENTION
15162     if (!cps->useSigint) return;
15163     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15164     switch (gameMode) {
15165       case MachinePlaysWhite:
15166       case MachinePlaysBlack:
15167       case TwoMachinesPlay:
15168       case IcsPlayingWhite:
15169       case IcsPlayingBlack:
15170       case AnalyzeMode:
15171       case AnalyzeFile:
15172         /* Skip if we know it isn't thinking */
15173         if (!cps->maybeThinking) return;
15174         if (appData.debugMode)
15175           fprintf(debugFP, "Interrupting %s\n", cps->which);
15176         InterruptChildProcess(cps->pr);
15177         cps->maybeThinking = FALSE;
15178         break;
15179       default:
15180         break;
15181     }
15182 #endif /*ATTENTION*/
15183 }
15184
15185 int
15186 CheckFlags()
15187 {
15188     if (whiteTimeRemaining <= 0) {
15189         if (!whiteFlag) {
15190             whiteFlag = TRUE;
15191             if (appData.icsActive) {
15192                 if (appData.autoCallFlag &&
15193                     gameMode == IcsPlayingBlack && !blackFlag) {
15194                   SendToICS(ics_prefix);
15195                   SendToICS("flag\n");
15196                 }
15197             } else {
15198                 if (blackFlag) {
15199                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15200                 } else {
15201                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15202                     if (appData.autoCallFlag) {
15203                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15204                         return TRUE;
15205                     }
15206                 }
15207             }
15208         }
15209     }
15210     if (blackTimeRemaining <= 0) {
15211         if (!blackFlag) {
15212             blackFlag = TRUE;
15213             if (appData.icsActive) {
15214                 if (appData.autoCallFlag &&
15215                     gameMode == IcsPlayingWhite && !whiteFlag) {
15216                   SendToICS(ics_prefix);
15217                   SendToICS("flag\n");
15218                 }
15219             } else {
15220                 if (whiteFlag) {
15221                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15222                 } else {
15223                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15224                     if (appData.autoCallFlag) {
15225                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15226                         return TRUE;
15227                     }
15228                 }
15229             }
15230         }
15231     }
15232     return FALSE;
15233 }
15234
15235 void
15236 CheckTimeControl()
15237 {
15238     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15239         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15240
15241     /*
15242      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15243      */
15244     if ( !WhiteOnMove(forwardMostMove) ) {
15245         /* White made time control */
15246         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15247         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15248         /* [HGM] time odds: correct new time quota for time odds! */
15249                                             / WhitePlayer()->timeOdds;
15250         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15251     } else {
15252         lastBlack -= blackTimeRemaining;
15253         /* Black made time control */
15254         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15255                                             / WhitePlayer()->other->timeOdds;
15256         lastWhite = whiteTimeRemaining;
15257     }
15258 }
15259
15260 void
15261 DisplayBothClocks()
15262 {
15263     int wom = gameMode == EditPosition ?
15264       !blackPlaysFirst : WhiteOnMove(currentMove);
15265     DisplayWhiteClock(whiteTimeRemaining, wom);
15266     DisplayBlackClock(blackTimeRemaining, !wom);
15267 }
15268
15269
15270 /* Timekeeping seems to be a portability nightmare.  I think everyone
15271    has ftime(), but I'm really not sure, so I'm including some ifdefs
15272    to use other calls if you don't.  Clocks will be less accurate if
15273    you have neither ftime nor gettimeofday.
15274 */
15275
15276 /* VS 2008 requires the #include outside of the function */
15277 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15278 #include <sys/timeb.h>
15279 #endif
15280
15281 /* Get the current time as a TimeMark */
15282 void
15283 GetTimeMark(tm)
15284      TimeMark *tm;
15285 {
15286 #if HAVE_GETTIMEOFDAY
15287
15288     struct timeval timeVal;
15289     struct timezone timeZone;
15290
15291     gettimeofday(&timeVal, &timeZone);
15292     tm->sec = (long) timeVal.tv_sec;
15293     tm->ms = (int) (timeVal.tv_usec / 1000L);
15294
15295 #else /*!HAVE_GETTIMEOFDAY*/
15296 #if HAVE_FTIME
15297
15298 // include <sys/timeb.h> / moved to just above start of function
15299     struct timeb timeB;
15300
15301     ftime(&timeB);
15302     tm->sec = (long) timeB.time;
15303     tm->ms = (int) timeB.millitm;
15304
15305 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15306     tm->sec = (long) time(NULL);
15307     tm->ms = 0;
15308 #endif
15309 #endif
15310 }
15311
15312 /* Return the difference in milliseconds between two
15313    time marks.  We assume the difference will fit in a long!
15314 */
15315 long
15316 SubtractTimeMarks(tm2, tm1)
15317      TimeMark *tm2, *tm1;
15318 {
15319     return 1000L*(tm2->sec - tm1->sec) +
15320            (long) (tm2->ms - tm1->ms);
15321 }
15322
15323
15324 /*
15325  * Code to manage the game clocks.
15326  *
15327  * In tournament play, black starts the clock and then white makes a move.
15328  * We give the human user a slight advantage if he is playing white---the
15329  * clocks don't run until he makes his first move, so it takes zero time.
15330  * Also, we don't account for network lag, so we could get out of sync
15331  * with GNU Chess's clock -- but then, referees are always right.
15332  */
15333
15334 static TimeMark tickStartTM;
15335 static long intendedTickLength;
15336
15337 long
15338 NextTickLength(timeRemaining)
15339      long timeRemaining;
15340 {
15341     long nominalTickLength, nextTickLength;
15342
15343     if (timeRemaining > 0L && timeRemaining <= 10000L)
15344       nominalTickLength = 100L;
15345     else
15346       nominalTickLength = 1000L;
15347     nextTickLength = timeRemaining % nominalTickLength;
15348     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15349
15350     return nextTickLength;
15351 }
15352
15353 /* Adjust clock one minute up or down */
15354 void
15355 AdjustClock(Boolean which, int dir)
15356 {
15357     if(which) blackTimeRemaining += 60000*dir;
15358     else      whiteTimeRemaining += 60000*dir;
15359     DisplayBothClocks();
15360 }
15361
15362 /* Stop clocks and reset to a fresh time control */
15363 void
15364 ResetClocks()
15365 {
15366     (void) StopClockTimer();
15367     if (appData.icsActive) {
15368         whiteTimeRemaining = blackTimeRemaining = 0;
15369     } else if (searchTime) {
15370         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15371         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15372     } else { /* [HGM] correct new time quote for time odds */
15373         whiteTC = blackTC = fullTimeControlString;
15374         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15375         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15376     }
15377     if (whiteFlag || blackFlag) {
15378         DisplayTitle("");
15379         whiteFlag = blackFlag = FALSE;
15380     }
15381     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15382     DisplayBothClocks();
15383 }
15384
15385 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15386
15387 /* Decrement running clock by amount of time that has passed */
15388 void
15389 DecrementClocks()
15390 {
15391     long timeRemaining;
15392     long lastTickLength, fudge;
15393     TimeMark now;
15394
15395     if (!appData.clockMode) return;
15396     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15397
15398     GetTimeMark(&now);
15399
15400     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15401
15402     /* Fudge if we woke up a little too soon */
15403     fudge = intendedTickLength - lastTickLength;
15404     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15405
15406     if (WhiteOnMove(forwardMostMove)) {
15407         if(whiteNPS >= 0) lastTickLength = 0;
15408         timeRemaining = whiteTimeRemaining -= lastTickLength;
15409         if(timeRemaining < 0 && !appData.icsActive) {
15410             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15411             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15412                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15413                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15414             }
15415         }
15416         DisplayWhiteClock(whiteTimeRemaining - fudge,
15417                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15418     } else {
15419         if(blackNPS >= 0) lastTickLength = 0;
15420         timeRemaining = blackTimeRemaining -= lastTickLength;
15421         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15422             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15423             if(suddenDeath) {
15424                 blackStartMove = forwardMostMove;
15425                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15426             }
15427         }
15428         DisplayBlackClock(blackTimeRemaining - fudge,
15429                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15430     }
15431     if (CheckFlags()) return;
15432
15433     tickStartTM = now;
15434     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15435     StartClockTimer(intendedTickLength);
15436
15437     /* if the time remaining has fallen below the alarm threshold, sound the
15438      * alarm. if the alarm has sounded and (due to a takeback or time control
15439      * with increment) the time remaining has increased to a level above the
15440      * threshold, reset the alarm so it can sound again.
15441      */
15442
15443     if (appData.icsActive && appData.icsAlarm) {
15444
15445         /* make sure we are dealing with the user's clock */
15446         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15447                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15448            )) return;
15449
15450         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15451             alarmSounded = FALSE;
15452         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15453             PlayAlarmSound();
15454             alarmSounded = TRUE;
15455         }
15456     }
15457 }
15458
15459
15460 /* A player has just moved, so stop the previously running
15461    clock and (if in clock mode) start the other one.
15462    We redisplay both clocks in case we're in ICS mode, because
15463    ICS gives us an update to both clocks after every move.
15464    Note that this routine is called *after* forwardMostMove
15465    is updated, so the last fractional tick must be subtracted
15466    from the color that is *not* on move now.
15467 */
15468 void
15469 SwitchClocks(int newMoveNr)
15470 {
15471     long lastTickLength;
15472     TimeMark now;
15473     int flagged = FALSE;
15474
15475     GetTimeMark(&now);
15476
15477     if (StopClockTimer() && appData.clockMode) {
15478         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15479         if (!WhiteOnMove(forwardMostMove)) {
15480             if(blackNPS >= 0) lastTickLength = 0;
15481             blackTimeRemaining -= lastTickLength;
15482            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15483 //         if(pvInfoList[forwardMostMove].time == -1)
15484                  pvInfoList[forwardMostMove].time =               // use GUI time
15485                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15486         } else {
15487            if(whiteNPS >= 0) lastTickLength = 0;
15488            whiteTimeRemaining -= lastTickLength;
15489            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15490 //         if(pvInfoList[forwardMostMove].time == -1)
15491                  pvInfoList[forwardMostMove].time =
15492                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15493         }
15494         flagged = CheckFlags();
15495     }
15496     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15497     CheckTimeControl();
15498
15499     if (flagged || !appData.clockMode) return;
15500
15501     switch (gameMode) {
15502       case MachinePlaysBlack:
15503       case MachinePlaysWhite:
15504       case BeginningOfGame:
15505         if (pausing) return;
15506         break;
15507
15508       case EditGame:
15509       case PlayFromGameFile:
15510       case IcsExamining:
15511         return;
15512
15513       default:
15514         break;
15515     }
15516
15517     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15518         if(WhiteOnMove(forwardMostMove))
15519              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15520         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15521     }
15522
15523     tickStartTM = now;
15524     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15525       whiteTimeRemaining : blackTimeRemaining);
15526     StartClockTimer(intendedTickLength);
15527 }
15528
15529
15530 /* Stop both clocks */
15531 void
15532 StopClocks()
15533 {
15534     long lastTickLength;
15535     TimeMark now;
15536
15537     if (!StopClockTimer()) return;
15538     if (!appData.clockMode) return;
15539
15540     GetTimeMark(&now);
15541
15542     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15543     if (WhiteOnMove(forwardMostMove)) {
15544         if(whiteNPS >= 0) lastTickLength = 0;
15545         whiteTimeRemaining -= lastTickLength;
15546         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15547     } else {
15548         if(blackNPS >= 0) lastTickLength = 0;
15549         blackTimeRemaining -= lastTickLength;
15550         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15551     }
15552     CheckFlags();
15553 }
15554
15555 /* Start clock of player on move.  Time may have been reset, so
15556    if clock is already running, stop and restart it. */
15557 void
15558 StartClocks()
15559 {
15560     (void) StopClockTimer(); /* in case it was running already */
15561     DisplayBothClocks();
15562     if (CheckFlags()) return;
15563
15564     if (!appData.clockMode) return;
15565     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15566
15567     GetTimeMark(&tickStartTM);
15568     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15569       whiteTimeRemaining : blackTimeRemaining);
15570
15571    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15572     whiteNPS = blackNPS = -1;
15573     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15574        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15575         whiteNPS = first.nps;
15576     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15577        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15578         blackNPS = first.nps;
15579     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15580         whiteNPS = second.nps;
15581     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15582         blackNPS = second.nps;
15583     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15584
15585     StartClockTimer(intendedTickLength);
15586 }
15587
15588 char *
15589 TimeString(ms)
15590      long ms;
15591 {
15592     long second, minute, hour, day;
15593     char *sign = "";
15594     static char buf[32];
15595
15596     if (ms > 0 && ms <= 9900) {
15597       /* convert milliseconds to tenths, rounding up */
15598       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15599
15600       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15601       return buf;
15602     }
15603
15604     /* convert milliseconds to seconds, rounding up */
15605     /* use floating point to avoid strangeness of integer division
15606        with negative dividends on many machines */
15607     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15608
15609     if (second < 0) {
15610         sign = "-";
15611         second = -second;
15612     }
15613
15614     day = second / (60 * 60 * 24);
15615     second = second % (60 * 60 * 24);
15616     hour = second / (60 * 60);
15617     second = second % (60 * 60);
15618     minute = second / 60;
15619     second = second % 60;
15620
15621     if (day > 0)
15622       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15623               sign, day, hour, minute, second);
15624     else if (hour > 0)
15625       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15626     else
15627       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15628
15629     return buf;
15630 }
15631
15632
15633 /*
15634  * This is necessary because some C libraries aren't ANSI C compliant yet.
15635  */
15636 char *
15637 StrStr(string, match)
15638      char *string, *match;
15639 {
15640     int i, length;
15641
15642     length = strlen(match);
15643
15644     for (i = strlen(string) - length; i >= 0; i--, string++)
15645       if (!strncmp(match, string, length))
15646         return string;
15647
15648     return NULL;
15649 }
15650
15651 char *
15652 StrCaseStr(string, match)
15653      char *string, *match;
15654 {
15655     int i, j, length;
15656
15657     length = strlen(match);
15658
15659     for (i = strlen(string) - length; i >= 0; i--, string++) {
15660         for (j = 0; j < length; j++) {
15661             if (ToLower(match[j]) != ToLower(string[j]))
15662               break;
15663         }
15664         if (j == length) return string;
15665     }
15666
15667     return NULL;
15668 }
15669
15670 #ifndef _amigados
15671 int
15672 StrCaseCmp(s1, s2)
15673      char *s1, *s2;
15674 {
15675     char c1, c2;
15676
15677     for (;;) {
15678         c1 = ToLower(*s1++);
15679         c2 = ToLower(*s2++);
15680         if (c1 > c2) return 1;
15681         if (c1 < c2) return -1;
15682         if (c1 == NULLCHAR) return 0;
15683     }
15684 }
15685
15686
15687 int
15688 ToLower(c)
15689      int c;
15690 {
15691     return isupper(c) ? tolower(c) : c;
15692 }
15693
15694
15695 int
15696 ToUpper(c)
15697      int c;
15698 {
15699     return islower(c) ? toupper(c) : c;
15700 }
15701 #endif /* !_amigados    */
15702
15703 char *
15704 StrSave(s)
15705      char *s;
15706 {
15707   char *ret;
15708
15709   if ((ret = (char *) malloc(strlen(s) + 1)))
15710     {
15711       safeStrCpy(ret, s, strlen(s)+1);
15712     }
15713   return ret;
15714 }
15715
15716 char *
15717 StrSavePtr(s, savePtr)
15718      char *s, **savePtr;
15719 {
15720     if (*savePtr) {
15721         free(*savePtr);
15722     }
15723     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15724       safeStrCpy(*savePtr, s, strlen(s)+1);
15725     }
15726     return(*savePtr);
15727 }
15728
15729 char *
15730 PGNDate()
15731 {
15732     time_t clock;
15733     struct tm *tm;
15734     char buf[MSG_SIZ];
15735
15736     clock = time((time_t *)NULL);
15737     tm = localtime(&clock);
15738     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15739             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15740     return StrSave(buf);
15741 }
15742
15743
15744 char *
15745 PositionToFEN(move, overrideCastling)
15746      int move;
15747      char *overrideCastling;
15748 {
15749     int i, j, fromX, fromY, toX, toY;
15750     int whiteToPlay;
15751     char buf[128];
15752     char *p, *q;
15753     int emptycount;
15754     ChessSquare piece;
15755
15756     whiteToPlay = (gameMode == EditPosition) ?
15757       !blackPlaysFirst : (move % 2 == 0);
15758     p = buf;
15759
15760     /* Piece placement data */
15761     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15762         emptycount = 0;
15763         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15764             if (boards[move][i][j] == EmptySquare) {
15765                 emptycount++;
15766             } else { ChessSquare piece = boards[move][i][j];
15767                 if (emptycount > 0) {
15768                     if(emptycount<10) /* [HGM] can be >= 10 */
15769                         *p++ = '0' + emptycount;
15770                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15771                     emptycount = 0;
15772                 }
15773                 if(PieceToChar(piece) == '+') {
15774                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15775                     *p++ = '+';
15776                     piece = (ChessSquare)(DEMOTED piece);
15777                 }
15778                 *p++ = PieceToChar(piece);
15779                 if(p[-1] == '~') {
15780                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15781                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15782                     *p++ = '~';
15783                 }
15784             }
15785         }
15786         if (emptycount > 0) {
15787             if(emptycount<10) /* [HGM] can be >= 10 */
15788                 *p++ = '0' + emptycount;
15789             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15790             emptycount = 0;
15791         }
15792         *p++ = '/';
15793     }
15794     *(p - 1) = ' ';
15795
15796     /* [HGM] print Crazyhouse or Shogi holdings */
15797     if( gameInfo.holdingsWidth ) {
15798         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15799         q = p;
15800         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15801             piece = boards[move][i][BOARD_WIDTH-1];
15802             if( piece != EmptySquare )
15803               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15804                   *p++ = PieceToChar(piece);
15805         }
15806         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15807             piece = boards[move][BOARD_HEIGHT-i-1][0];
15808             if( piece != EmptySquare )
15809               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15810                   *p++ = PieceToChar(piece);
15811         }
15812
15813         if( q == p ) *p++ = '-';
15814         *p++ = ']';
15815         *p++ = ' ';
15816     }
15817
15818     /* Active color */
15819     *p++ = whiteToPlay ? 'w' : 'b';
15820     *p++ = ' ';
15821
15822   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15823     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15824   } else {
15825   if(nrCastlingRights) {
15826      q = p;
15827      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15828        /* [HGM] write directly from rights */
15829            if(boards[move][CASTLING][2] != NoRights &&
15830               boards[move][CASTLING][0] != NoRights   )
15831                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15832            if(boards[move][CASTLING][2] != NoRights &&
15833               boards[move][CASTLING][1] != NoRights   )
15834                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15835            if(boards[move][CASTLING][5] != NoRights &&
15836               boards[move][CASTLING][3] != NoRights   )
15837                 *p++ = boards[move][CASTLING][3] + AAA;
15838            if(boards[move][CASTLING][5] != NoRights &&
15839               boards[move][CASTLING][4] != NoRights   )
15840                 *p++ = boards[move][CASTLING][4] + AAA;
15841      } else {
15842
15843         /* [HGM] write true castling rights */
15844         if( nrCastlingRights == 6 ) {
15845             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15846                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15847             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15848                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15849             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15850                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15851             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15852                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15853         }
15854      }
15855      if (q == p) *p++ = '-'; /* No castling rights */
15856      *p++ = ' ';
15857   }
15858
15859   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15860      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15861     /* En passant target square */
15862     if (move > backwardMostMove) {
15863         fromX = moveList[move - 1][0] - AAA;
15864         fromY = moveList[move - 1][1] - ONE;
15865         toX = moveList[move - 1][2] - AAA;
15866         toY = moveList[move - 1][3] - ONE;
15867         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15868             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15869             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15870             fromX == toX) {
15871             /* 2-square pawn move just happened */
15872             *p++ = toX + AAA;
15873             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15874         } else {
15875             *p++ = '-';
15876         }
15877     } else if(move == backwardMostMove) {
15878         // [HGM] perhaps we should always do it like this, and forget the above?
15879         if((signed char)boards[move][EP_STATUS] >= 0) {
15880             *p++ = boards[move][EP_STATUS] + AAA;
15881             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15882         } else {
15883             *p++ = '-';
15884         }
15885     } else {
15886         *p++ = '-';
15887     }
15888     *p++ = ' ';
15889   }
15890   }
15891
15892     /* [HGM] find reversible plies */
15893     {   int i = 0, j=move;
15894
15895         if (appData.debugMode) { int k;
15896             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15897             for(k=backwardMostMove; k<=forwardMostMove; k++)
15898                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15899
15900         }
15901
15902         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15903         if( j == backwardMostMove ) i += initialRulePlies;
15904         sprintf(p, "%d ", i);
15905         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15906     }
15907     /* Fullmove number */
15908     sprintf(p, "%d", (move / 2) + 1);
15909
15910     return StrSave(buf);
15911 }
15912
15913 Boolean
15914 ParseFEN(board, blackPlaysFirst, fen)
15915     Board board;
15916      int *blackPlaysFirst;
15917      char *fen;
15918 {
15919     int i, j;
15920     char *p, c;
15921     int emptycount;
15922     ChessSquare piece;
15923
15924     p = fen;
15925
15926     /* [HGM] by default clear Crazyhouse holdings, if present */
15927     if(gameInfo.holdingsWidth) {
15928        for(i=0; i<BOARD_HEIGHT; i++) {
15929            board[i][0]             = EmptySquare; /* black holdings */
15930            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15931            board[i][1]             = (ChessSquare) 0; /* black counts */
15932            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15933        }
15934     }
15935
15936     /* Piece placement data */
15937     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15938         j = 0;
15939         for (;;) {
15940             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15941                 if (*p == '/') p++;
15942                 emptycount = gameInfo.boardWidth - j;
15943                 while (emptycount--)
15944                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15945                 break;
15946 #if(BOARD_FILES >= 10)
15947             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15948                 p++; emptycount=10;
15949                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15950                 while (emptycount--)
15951                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15952 #endif
15953             } else if (isdigit(*p)) {
15954                 emptycount = *p++ - '0';
15955                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15956                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15957                 while (emptycount--)
15958                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15959             } else if (*p == '+' || isalpha(*p)) {
15960                 if (j >= gameInfo.boardWidth) return FALSE;
15961                 if(*p=='+') {
15962                     piece = CharToPiece(*++p);
15963                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15964                     piece = (ChessSquare) (PROMOTED piece ); p++;
15965                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15966                 } else piece = CharToPiece(*p++);
15967
15968                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15969                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15970                     piece = (ChessSquare) (PROMOTED piece);
15971                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15972                     p++;
15973                 }
15974                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15975             } else {
15976                 return FALSE;
15977             }
15978         }
15979     }
15980     while (*p == '/' || *p == ' ') p++;
15981
15982     /* [HGM] look for Crazyhouse holdings here */
15983     while(*p==' ') p++;
15984     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15985         if(*p == '[') p++;
15986         if(*p == '-' ) p++; /* empty holdings */ else {
15987             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15988             /* if we would allow FEN reading to set board size, we would   */
15989             /* have to add holdings and shift the board read so far here   */
15990             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15991                 p++;
15992                 if((int) piece >= (int) BlackPawn ) {
15993                     i = (int)piece - (int)BlackPawn;
15994                     i = PieceToNumber((ChessSquare)i);
15995                     if( i >= gameInfo.holdingsSize ) return FALSE;
15996                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15997                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15998                 } else {
15999                     i = (int)piece - (int)WhitePawn;
16000                     i = PieceToNumber((ChessSquare)i);
16001                     if( i >= gameInfo.holdingsSize ) return FALSE;
16002                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16003                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16004                 }
16005             }
16006         }
16007         if(*p == ']') p++;
16008     }
16009
16010     while(*p == ' ') p++;
16011
16012     /* Active color */
16013     c = *p++;
16014     if(appData.colorNickNames) {
16015       if( c == appData.colorNickNames[0] ) c = 'w'; else
16016       if( c == appData.colorNickNames[1] ) c = 'b';
16017     }
16018     switch (c) {
16019       case 'w':
16020         *blackPlaysFirst = FALSE;
16021         break;
16022       case 'b':
16023         *blackPlaysFirst = TRUE;
16024         break;
16025       default:
16026         return FALSE;
16027     }
16028
16029     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16030     /* return the extra info in global variiables             */
16031
16032     /* set defaults in case FEN is incomplete */
16033     board[EP_STATUS] = EP_UNKNOWN;
16034     for(i=0; i<nrCastlingRights; i++ ) {
16035         board[CASTLING][i] =
16036             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16037     }   /* assume possible unless obviously impossible */
16038     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16039     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16040     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16041                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16042     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16043     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16044     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16045                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16046     FENrulePlies = 0;
16047
16048     while(*p==' ') p++;
16049     if(nrCastlingRights) {
16050       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16051           /* castling indicator present, so default becomes no castlings */
16052           for(i=0; i<nrCastlingRights; i++ ) {
16053                  board[CASTLING][i] = NoRights;
16054           }
16055       }
16056       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16057              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16058              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16059              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16060         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16061
16062         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16063             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16064             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16065         }
16066         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16067             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16068         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16069                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16070         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16071                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16072         switch(c) {
16073           case'K':
16074               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16075               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16076               board[CASTLING][2] = whiteKingFile;
16077               break;
16078           case'Q':
16079               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16080               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16081               board[CASTLING][2] = whiteKingFile;
16082               break;
16083           case'k':
16084               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16085               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16086               board[CASTLING][5] = blackKingFile;
16087               break;
16088           case'q':
16089               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16090               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16091               board[CASTLING][5] = blackKingFile;
16092           case '-':
16093               break;
16094           default: /* FRC castlings */
16095               if(c >= 'a') { /* black rights */
16096                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16097                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16098                   if(i == BOARD_RGHT) break;
16099                   board[CASTLING][5] = i;
16100                   c -= AAA;
16101                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16102                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16103                   if(c > i)
16104                       board[CASTLING][3] = c;
16105                   else
16106                       board[CASTLING][4] = c;
16107               } else { /* white rights */
16108                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16109                     if(board[0][i] == WhiteKing) break;
16110                   if(i == BOARD_RGHT) break;
16111                   board[CASTLING][2] = i;
16112                   c -= AAA - 'a' + 'A';
16113                   if(board[0][c] >= WhiteKing) break;
16114                   if(c > i)
16115                       board[CASTLING][0] = c;
16116                   else
16117                       board[CASTLING][1] = c;
16118               }
16119         }
16120       }
16121       for(i=0; i<nrCastlingRights; i++)
16122         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16123     if (appData.debugMode) {
16124         fprintf(debugFP, "FEN castling rights:");
16125         for(i=0; i<nrCastlingRights; i++)
16126         fprintf(debugFP, " %d", board[CASTLING][i]);
16127         fprintf(debugFP, "\n");
16128     }
16129
16130       while(*p==' ') p++;
16131     }
16132
16133     /* read e.p. field in games that know e.p. capture */
16134     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16135        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16136       if(*p=='-') {
16137         p++; board[EP_STATUS] = EP_NONE;
16138       } else {
16139          char c = *p++ - AAA;
16140
16141          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16142          if(*p >= '0' && *p <='9') p++;
16143          board[EP_STATUS] = c;
16144       }
16145     }
16146
16147
16148     if(sscanf(p, "%d", &i) == 1) {
16149         FENrulePlies = i; /* 50-move ply counter */
16150         /* (The move number is still ignored)    */
16151     }
16152
16153     return TRUE;
16154 }
16155
16156 void
16157 EditPositionPasteFEN(char *fen)
16158 {
16159   if (fen != NULL) {
16160     Board initial_position;
16161
16162     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16163       DisplayError(_("Bad FEN position in clipboard"), 0);
16164       return ;
16165     } else {
16166       int savedBlackPlaysFirst = blackPlaysFirst;
16167       EditPositionEvent();
16168       blackPlaysFirst = savedBlackPlaysFirst;
16169       CopyBoard(boards[0], initial_position);
16170       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16171       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16172       DisplayBothClocks();
16173       DrawPosition(FALSE, boards[currentMove]);
16174     }
16175   }
16176 }
16177
16178 static char cseq[12] = "\\   ";
16179
16180 Boolean set_cont_sequence(char *new_seq)
16181 {
16182     int len;
16183     Boolean ret;
16184
16185     // handle bad attempts to set the sequence
16186         if (!new_seq)
16187                 return 0; // acceptable error - no debug
16188
16189     len = strlen(new_seq);
16190     ret = (len > 0) && (len < sizeof(cseq));
16191     if (ret)
16192       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16193     else if (appData.debugMode)
16194       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16195     return ret;
16196 }
16197
16198 /*
16199     reformat a source message so words don't cross the width boundary.  internal
16200     newlines are not removed.  returns the wrapped size (no null character unless
16201     included in source message).  If dest is NULL, only calculate the size required
16202     for the dest buffer.  lp argument indicats line position upon entry, and it's
16203     passed back upon exit.
16204 */
16205 int wrap(char *dest, char *src, int count, int width, int *lp)
16206 {
16207     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16208
16209     cseq_len = strlen(cseq);
16210     old_line = line = *lp;
16211     ansi = len = clen = 0;
16212
16213     for (i=0; i < count; i++)
16214     {
16215         if (src[i] == '\033')
16216             ansi = 1;
16217
16218         // if we hit the width, back up
16219         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16220         {
16221             // store i & len in case the word is too long
16222             old_i = i, old_len = len;
16223
16224             // find the end of the last word
16225             while (i && src[i] != ' ' && src[i] != '\n')
16226             {
16227                 i--;
16228                 len--;
16229             }
16230
16231             // word too long?  restore i & len before splitting it
16232             if ((old_i-i+clen) >= width)
16233             {
16234                 i = old_i;
16235                 len = old_len;
16236             }
16237
16238             // extra space?
16239             if (i && src[i-1] == ' ')
16240                 len--;
16241
16242             if (src[i] != ' ' && src[i] != '\n')
16243             {
16244                 i--;
16245                 if (len)
16246                     len--;
16247             }
16248
16249             // now append the newline and continuation sequence
16250             if (dest)
16251                 dest[len] = '\n';
16252             len++;
16253             if (dest)
16254                 strncpy(dest+len, cseq, cseq_len);
16255             len += cseq_len;
16256             line = cseq_len;
16257             clen = cseq_len;
16258             continue;
16259         }
16260
16261         if (dest)
16262             dest[len] = src[i];
16263         len++;
16264         if (!ansi)
16265             line++;
16266         if (src[i] == '\n')
16267             line = 0;
16268         if (src[i] == 'm')
16269             ansi = 0;
16270     }
16271     if (dest && appData.debugMode)
16272     {
16273         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16274             count, width, line, len, *lp);
16275         show_bytes(debugFP, src, count);
16276         fprintf(debugFP, "\ndest: ");
16277         show_bytes(debugFP, dest, len);
16278         fprintf(debugFP, "\n");
16279     }
16280     *lp = dest ? line : old_line;
16281
16282     return len;
16283 }
16284
16285 // [HGM] vari: routines for shelving variations
16286
16287 void
16288 PushInner(int firstMove, int lastMove)
16289 {
16290         int i, j, nrMoves = lastMove - firstMove;
16291
16292         // push current tail of game on stack
16293         savedResult[storedGames] = gameInfo.result;
16294         savedDetails[storedGames] = gameInfo.resultDetails;
16295         gameInfo.resultDetails = NULL;
16296         savedFirst[storedGames] = firstMove;
16297         savedLast [storedGames] = lastMove;
16298         savedFramePtr[storedGames] = framePtr;
16299         framePtr -= nrMoves; // reserve space for the boards
16300         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16301             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16302             for(j=0; j<MOVE_LEN; j++)
16303                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16304             for(j=0; j<2*MOVE_LEN; j++)
16305                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16306             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16307             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16308             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16309             pvInfoList[firstMove+i-1].depth = 0;
16310             commentList[framePtr+i] = commentList[firstMove+i];
16311             commentList[firstMove+i] = NULL;
16312         }
16313
16314         storedGames++;
16315         forwardMostMove = firstMove; // truncate game so we can start variation
16316 }
16317
16318 void
16319 PushTail(int firstMove, int lastMove)
16320 {
16321         if(appData.icsActive) { // only in local mode
16322                 forwardMostMove = currentMove; // mimic old ICS behavior
16323                 return;
16324         }
16325         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16326
16327         PushInner(firstMove, lastMove);
16328         if(storedGames == 1) GreyRevert(FALSE);
16329 }
16330
16331 void
16332 PopInner(Boolean annotate)
16333 {
16334         int i, j, nrMoves;
16335         char buf[8000], moveBuf[20];
16336
16337         storedGames--;
16338         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16339         nrMoves = savedLast[storedGames] - currentMove;
16340         if(annotate) {
16341                 int cnt = 10;
16342                 if(!WhiteOnMove(currentMove))
16343                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16344                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16345                 for(i=currentMove; i<forwardMostMove; i++) {
16346                         if(WhiteOnMove(i))
16347                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16348                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16349                         strcat(buf, moveBuf);
16350                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16351                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16352                 }
16353                 strcat(buf, ")");
16354         }
16355         for(i=1; i<=nrMoves; i++) { // copy last variation back
16356             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16357             for(j=0; j<MOVE_LEN; j++)
16358                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16359             for(j=0; j<2*MOVE_LEN; j++)
16360                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16361             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16362             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16363             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16364             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16365             commentList[currentMove+i] = commentList[framePtr+i];
16366             commentList[framePtr+i] = NULL;
16367         }
16368         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16369         framePtr = savedFramePtr[storedGames];
16370         gameInfo.result = savedResult[storedGames];
16371         if(gameInfo.resultDetails != NULL) {
16372             free(gameInfo.resultDetails);
16373       }
16374         gameInfo.resultDetails = savedDetails[storedGames];
16375         forwardMostMove = currentMove + nrMoves;
16376 }
16377
16378 Boolean
16379 PopTail(Boolean annotate)
16380 {
16381         if(appData.icsActive) return FALSE; // only in local mode
16382         if(!storedGames) return FALSE; // sanity
16383         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16384
16385         PopInner(annotate);
16386
16387         if(storedGames == 0) GreyRevert(TRUE);
16388         return TRUE;
16389 }
16390
16391 void
16392 CleanupTail()
16393 {       // remove all shelved variations
16394         int i;
16395         for(i=0; i<storedGames; i++) {
16396             if(savedDetails[i])
16397                 free(savedDetails[i]);
16398             savedDetails[i] = NULL;
16399         }
16400         for(i=framePtr; i<MAX_MOVES; i++) {
16401                 if(commentList[i]) free(commentList[i]);
16402                 commentList[i] = NULL;
16403         }
16404         framePtr = MAX_MOVES-1;
16405         storedGames = 0;
16406 }
16407
16408 void
16409 LoadVariation(int index, char *text)
16410 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16411         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16412         int level = 0, move;
16413
16414         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16415         // first find outermost bracketing variation
16416         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16417             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16418                 if(*p == '{') wait = '}'; else
16419                 if(*p == '[') wait = ']'; else
16420                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16421                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16422             }
16423             if(*p == wait) wait = NULLCHAR; // closing ]} found
16424             p++;
16425         }
16426         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16427         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16428         end[1] = NULLCHAR; // clip off comment beyond variation
16429         ToNrEvent(currentMove-1);
16430         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16431         // kludge: use ParsePV() to append variation to game
16432         move = currentMove;
16433         ParsePV(start, TRUE, TRUE);
16434         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16435         ClearPremoveHighlights();
16436         CommentPopDown();
16437         ToNrEvent(currentMove+1);
16438 }
16439