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