f028af444d38b2a135e3ccb2ee67f56e0c9948da
[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 void DisplayTwoMachinesTitle P(());
238
239 #ifdef WIN32
240        extern void ConsoleCreate();
241 #endif
242
243 ChessProgramState *WhitePlayer();
244 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
245 int VerifyDisplayMode P(());
246
247 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
248 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
249 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
250 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
251 void ics_update_width P((int new_width));
252 extern char installDir[MSG_SIZ];
253 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 Boolean abortMatch;
255
256 extern int tinyLayout, smallLayout;
257 ChessProgramStats programStats;
258 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 int endPV = -1;
260 static int exiting = 0; /* [HGM] moved to top */
261 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
262 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
263 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
264 int partnerHighlight[2];
265 Boolean partnerBoardValid = 0;
266 char partnerStatus[MSG_SIZ];
267 Boolean partnerUp;
268 Boolean originalFlip;
269 Boolean twoBoards = 0;
270 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
271 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
272 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
273 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
274 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
275 int opponentKibitzes;
276 int lastSavedGame; /* [HGM] save: ID of game */
277 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
278 extern int chatCount;
279 int chattingPartner;
280 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
281 char lastMsg[MSG_SIZ];
282 ChessSquare pieceSweep = EmptySquare;
283 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
284 int promoDefaultAltered;
285
286 /* States for ics_getting_history */
287 #define H_FALSE 0
288 #define H_REQUESTED 1
289 #define H_GOT_REQ_HEADER 2
290 #define H_GOT_UNREQ_HEADER 3
291 #define H_GETTING_MOVES 4
292 #define H_GOT_UNWANTED_HEADER 5
293
294 /* whosays values for GameEnds */
295 #define GE_ICS 0
296 #define GE_ENGINE 1
297 #define GE_PLAYER 2
298 #define GE_FILE 3
299 #define GE_XBOARD 4
300 #define GE_ENGINE1 5
301 #define GE_ENGINE2 6
302
303 /* Maximum number of games in a cmail message */
304 #define CMAIL_MAX_GAMES 20
305
306 /* Different types of move when calling RegisterMove */
307 #define CMAIL_MOVE   0
308 #define CMAIL_RESIGN 1
309 #define CMAIL_DRAW   2
310 #define CMAIL_ACCEPT 3
311
312 /* Different types of result to remember for each game */
313 #define CMAIL_NOT_RESULT 0
314 #define CMAIL_OLD_RESULT 1
315 #define CMAIL_NEW_RESULT 2
316
317 /* Telnet protocol constants */
318 #define TN_WILL 0373
319 #define TN_WONT 0374
320 #define TN_DO   0375
321 #define TN_DONT 0376
322 #define TN_IAC  0377
323 #define TN_ECHO 0001
324 #define TN_SGA  0003
325 #define TN_PORT 23
326
327 char*
328 safeStrCpy( char *dst, const char *src, size_t count )
329 { // [HGM] made safe
330   int i;
331   assert( dst != NULL );
332   assert( src != NULL );
333   assert( count > 0 );
334
335   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
336   if(  i == count && dst[count-1] != NULLCHAR)
337     {
338       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
339       if(appData.debugMode)
340       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
341     }
342
343   return dst;
344 }
345
346 /* Some compiler can't cast u64 to double
347  * This function do the job for us:
348
349  * We use the highest bit for cast, this only
350  * works if the highest bit is not
351  * in use (This should not happen)
352  *
353  * We used this for all compiler
354  */
355 double
356 u64ToDouble(u64 value)
357 {
358   double r;
359   u64 tmp = value & u64Const(0x7fffffffffffffff);
360   r = (double)(s64)tmp;
361   if (value & u64Const(0x8000000000000000))
362        r +=  9.2233720368547758080e18; /* 2^63 */
363  return r;
364 }
365
366 /* Fake up flags for now, as we aren't keeping track of castling
367    availability yet. [HGM] Change of logic: the flag now only
368    indicates the type of castlings allowed by the rule of the game.
369    The actual rights themselves are maintained in the array
370    castlingRights, as part of the game history, and are not probed
371    by this function.
372  */
373 int
374 PosFlags(index)
375 {
376   int flags = F_ALL_CASTLE_OK;
377   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
378   switch (gameInfo.variant) {
379   case VariantSuicide:
380     flags &= ~F_ALL_CASTLE_OK;
381   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
382     flags |= F_IGNORE_CHECK;
383   case VariantLosers:
384     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385     break;
386   case VariantAtomic:
387     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388     break;
389   case VariantKriegspiel:
390     flags |= F_KRIEGSPIEL_CAPTURE;
391     break;
392   case VariantCapaRandom:
393   case VariantFischeRandom:
394     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
395   case VariantNoCastle:
396   case VariantShatranj:
397   case VariantCourier:
398   case VariantMakruk:
399     flags &= ~F_ALL_CASTLE_OK;
400     break;
401   default:
402     break;
403   }
404   return flags;
405 }
406
407 FILE *gameFileFP, *debugFP;
408
409 /*
410     [AS] Note: sometimes, the sscanf() function is used to parse the input
411     into a fixed-size buffer. Because of this, we must be prepared to
412     receive strings as long as the size of the input buffer, which is currently
413     set to 4K for Windows and 8K for the rest.
414     So, we must either allocate sufficiently large buffers here, or
415     reduce the size of the input buffer in the input reading part.
416 */
417
418 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
419 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
420 char thinkOutput1[MSG_SIZ*10];
421
422 ChessProgramState first, second, pairing;
423
424 /* premove variables */
425 int premoveToX = 0;
426 int premoveToY = 0;
427 int premoveFromX = 0;
428 int premoveFromY = 0;
429 int premovePromoChar = 0;
430 int gotPremove = 0;
431 Boolean alarmSounded;
432 /* end premove variables */
433
434 char *ics_prefix = "$";
435 int ics_type = ICS_GENERIC;
436
437 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
438 int pauseExamForwardMostMove = 0;
439 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
440 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
441 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
442 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
443 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
444 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
445 int whiteFlag = FALSE, blackFlag = FALSE;
446 int userOfferedDraw = FALSE;
447 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
448 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
449 int cmailMoveType[CMAIL_MAX_GAMES];
450 long ics_clock_paused = 0;
451 ProcRef icsPR = NoProc, cmailPR = NoProc;
452 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
453 GameMode gameMode = BeginningOfGame;
454 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
455 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
456 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
457 int hiddenThinkOutputState = 0; /* [AS] */
458 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
459 int adjudicateLossPlies = 6;
460 char white_holding[64], black_holding[64];
461 TimeMark lastNodeCountTime;
462 long lastNodeCount=0;
463 int shiftKey; // [HGM] set by mouse handler
464
465 int have_sent_ICS_logon = 0;
466 int movesPerSession;
467 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
468 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
469 long timeControl_2; /* [AS] Allow separate time controls */
470 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
471 long timeRemaining[2][MAX_MOVES];
472 int matchGame = 0, nextGame = 0, roundNr = 0;
473 Boolean waitingForGame = FALSE;
474 TimeMark programStartTime, pauseStart;
475 char ics_handle[MSG_SIZ];
476 int have_set_title = 0;
477
478 /* animateTraining preserves the state of appData.animate
479  * when Training mode is activated. This allows the
480  * response to be animated when appData.animate == TRUE and
481  * appData.animateDragging == TRUE.
482  */
483 Boolean animateTraining;
484
485 GameInfo gameInfo;
486
487 AppData appData;
488
489 Board boards[MAX_MOVES];
490 /* [HGM] Following 7 needed for accurate legality tests: */
491 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
492 signed char  initialRights[BOARD_FILES];
493 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
494 int   initialRulePlies, FENrulePlies;
495 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int loadFlag = 0;
497 Boolean shuffleOpenings;
498 int mute; // mute all sounds
499
500 // [HGM] vari: next 12 to save and restore variations
501 #define MAX_VARIATIONS 10
502 int framePtr = MAX_MOVES-1; // points to free stack entry
503 int storedGames = 0;
504 int savedFirst[MAX_VARIATIONS];
505 int savedLast[MAX_VARIATIONS];
506 int savedFramePtr[MAX_VARIATIONS];
507 char *savedDetails[MAX_VARIATIONS];
508 ChessMove savedResult[MAX_VARIATIONS];
509
510 void PushTail P((int firstMove, int lastMove));
511 Boolean PopTail P((Boolean annotate));
512 void PushInner P((int firstMove, int lastMove));
513 void PopInner P((Boolean annotate));
514 void CleanupTail P((void));
515
516 ChessSquare  FIDEArray[2][BOARD_FILES] = {
517     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
519     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520         BlackKing, BlackBishop, BlackKnight, BlackRook }
521 };
522
523 ChessSquare twoKingsArray[2][BOARD_FILES] = {
524     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
525         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
526     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
527         BlackKing, BlackKing, BlackKnight, BlackRook }
528 };
529
530 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
531     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
532         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
533     { BlackRook, BlackMan, BlackBishop, BlackQueen,
534         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 };
536
537 ChessSquare SpartanArray[2][BOARD_FILES] = {
538     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
541         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 };
543
544 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
545     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
548         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 };
550
551 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
553         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
555         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 };
557
558 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
559     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
560         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackMan, BlackFerz,
562         BlackKing, BlackMan, BlackKnight, BlackRook }
563 };
564
565
566 #if (BOARD_FILES>=10)
567 ChessSquare ShogiArray[2][BOARD_FILES] = {
568     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
569         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
570     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
571         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 };
573
574 ChessSquare XiangqiArray[2][BOARD_FILES] = {
575     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
576         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
578         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 };
580
581 ChessSquare CapablancaArray[2][BOARD_FILES] = {
582     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
583         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
585         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 };
587
588 ChessSquare GreatArray[2][BOARD_FILES] = {
589     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
590         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
591     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
592         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 };
594
595 ChessSquare JanusArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
597         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
598     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
599         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
600 };
601
602 #ifdef GOTHIC
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !GOTHIC
610 #define GothicArray CapablancaArray
611 #endif // !GOTHIC
612
613 #ifdef FALCON
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
616         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
618         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 };
620 #else // !FALCON
621 #define FalconArray CapablancaArray
622 #endif // !FALCON
623
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
630
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 };
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
641
642
643 Board initialPosition;
644
645
646 /* Convert str to a rating. Checks for special cases of "----",
647
648    "++++", etc. Also strips ()'s */
649 int
650 string_to_rating(str)
651   char *str;
652 {
653   while(*str && !isdigit(*str)) ++str;
654   if (!*str)
655     return 0;   /* One of the special "no rating" cases */
656   else
657     return atoi(str);
658 }
659
660 void
661 ClearProgramStats()
662 {
663     /* Init programStats */
664     programStats.movelist[0] = 0;
665     programStats.depth = 0;
666     programStats.nr_moves = 0;
667     programStats.moves_left = 0;
668     programStats.nodes = 0;
669     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
670     programStats.score = 0;
671     programStats.got_only_move = 0;
672     programStats.got_fail = 0;
673     programStats.line_is_book = 0;
674 }
675
676 void
677 CommonEngineInit()
678 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679     if (appData.firstPlaysBlack) {
680         first.twoMachinesColor = "black\n";
681         second.twoMachinesColor = "white\n";
682     } else {
683         first.twoMachinesColor = "white\n";
684         second.twoMachinesColor = "black\n";
685     }
686
687     first.other = &second;
688     second.other = &first;
689
690     { float norm = 1;
691         if(appData.timeOddsMode) {
692             norm = appData.timeOdds[0];
693             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694         }
695         first.timeOdds  = appData.timeOdds[0]/norm;
696         second.timeOdds = appData.timeOdds[1]/norm;
697     }
698
699     if(programVersion) free(programVersion);
700     if (appData.noChessProgram) {
701         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702         sprintf(programVersion, "%s", PACKAGE_STRING);
703     } else {
704       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707     }
708 }
709
710 void
711 UnloadEngine(ChessProgramState *cps)
712 {
713         /* Kill off first chess program */
714         if (cps->isr != NULL)
715           RemoveInputSource(cps->isr);
716         cps->isr = NULL;
717
718         if (cps->pr != NoProc) {
719             ExitAnalyzeMode();
720             DoSleep( appData.delayBeforeQuit );
721             SendToProgram("quit\n", cps);
722             DoSleep( appData.delayAfterQuit );
723             DestroyChildProcess(cps->pr, cps->useSigterm);
724         }
725         cps->pr = NoProc;
726         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 }
728
729 void
730 ClearOptions(ChessProgramState *cps)
731 {
732     int i;
733     cps->nrOptions = cps->comboCnt = 0;
734     for(i=0; i<MAX_OPTIONS; i++) {
735         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736         cps->option[i].textValue = 0;
737     }
738 }
739
740 char *engineNames[] = {
741 "first",
742 "second"
743 };
744
745 void
746 InitEngine(ChessProgramState *cps, int n)
747 {   // [HGM] all engine initialiation put in a function that does one engine
748
749     ClearOptions(cps);
750
751     cps->which = engineNames[n];
752     cps->maybeThinking = FALSE;
753     cps->pr = NoProc;
754     cps->isr = NULL;
755     cps->sendTime = 2;
756     cps->sendDrawOffers = 1;
757
758     cps->program = appData.chessProgram[n];
759     cps->host = appData.host[n];
760     cps->dir = appData.directory[n];
761     cps->initString = appData.engInitString[n];
762     cps->computerString = appData.computerString[n];
763     cps->useSigint  = TRUE;
764     cps->useSigterm = TRUE;
765     cps->reuse = appData.reuse[n];
766     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
767     cps->useSetboard = FALSE;
768     cps->useSAN = FALSE;
769     cps->usePing = FALSE;
770     cps->lastPing = 0;
771     cps->lastPong = 0;
772     cps->usePlayother = FALSE;
773     cps->useColors = TRUE;
774     cps->useUsermove = FALSE;
775     cps->sendICS = FALSE;
776     cps->sendName = appData.icsActive;
777     cps->sdKludge = FALSE;
778     cps->stKludge = FALSE;
779     TidyProgramName(cps->program, cps->host, cps->tidy);
780     cps->matchWins = 0;
781     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782     cps->analysisSupport = 2; /* detect */
783     cps->analyzing = FALSE;
784     cps->initDone = FALSE;
785
786     /* New features added by Tord: */
787     cps->useFEN960 = FALSE;
788     cps->useOOCastle = TRUE;
789     /* End of new features added by Tord. */
790     cps->fenOverride  = appData.fenOverride[n];
791
792     /* [HGM] time odds: set factor for each machine */
793     cps->timeOdds  = appData.timeOdds[n];
794
795     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796     cps->accumulateTC = appData.accumulateTC[n];
797     cps->maxNrOfSessions = 1;
798
799     /* [HGM] debug */
800     cps->debug = FALSE;
801
802     cps->supportsNPS = UNKNOWN;
803     cps->memSize = FALSE;
804     cps->maxCores = FALSE;
805     cps->egtFormats[0] = NULLCHAR;
806
807     /* [HGM] options */
808     cps->optionSettings  = appData.engOptions[n];
809
810     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811     cps->isUCI = appData.isUCI[n]; /* [AS] */
812     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813
814     if (appData.protocolVersion[n] > PROTOVER
815         || appData.protocolVersion[n] < 1)
816       {
817         char buf[MSG_SIZ];
818         int len;
819
820         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821                        appData.protocolVersion[n]);
822         if( (len > MSG_SIZ) && appData.debugMode )
823           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824
825         DisplayFatalError(buf, 0, 2);
826       }
827     else
828       {
829         cps->protocolVersion = appData.protocolVersion[n];
830       }
831
832     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
833 }
834
835 ChessProgramState *savCps;
836
837 void
838 LoadEngine()
839 {
840     int i;
841     if(WaitForEngine(savCps, LoadEngine)) return;
842     CommonEngineInit(); // recalculate time odds
843     if(gameInfo.variant != StringToVariant(appData.variant)) {
844         // we changed variant when loading the engine; this forces us to reset
845         Reset(TRUE, savCps != &first);
846         EditGameEvent(); // for consistency with other path, as Reset changes mode
847     }
848     InitChessProgram(savCps, FALSE);
849     SendToProgram("force\n", savCps);
850     DisplayMessage("", "");
851     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
852     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
853     ThawUI();
854     SetGNUMode();
855 }
856
857 void
858 ReplaceEngine(ChessProgramState *cps, int n)
859 {
860     EditGameEvent();
861     UnloadEngine(cps);
862     appData.noChessProgram = FALSE;
863     appData.clockMode = TRUE;
864     InitEngine(cps, n);
865     UpdateLogos(TRUE);
866     if(n) return; // only startup first engine immediately; second can wait
867     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
868     LoadEngine();
869 }
870
871 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
872 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
873
874 static char resetOptions[] = 
875         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
876         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877
878 void
879 Load(ChessProgramState *cps, int i)
880 {
881     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
882     if(engineLine[0]) { // an engine was selected from the combo box
883         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
884         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
885         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
886         ParseArgsFromString(buf);
887         SwapEngines(i);
888         ReplaceEngine(cps, i);
889         return;
890     }
891     p = engineName;
892     while(q = strchr(p, SLASH)) p = q+1;
893     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
894     if(engineDir[0] != NULLCHAR)
895         appData.directory[i] = engineDir;
896     else if(p != engineName) { // derive directory from engine path, when not given
897         p[-1] = 0;
898         appData.directory[i] = strdup(engineName);
899         p[-1] = SLASH;
900     } else appData.directory[i] = ".";
901     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
902     if(params[0]) {
903         snprintf(command, MSG_SIZ, "%s %s", p, params);
904         p = command;
905     }
906     appData.chessProgram[i] = strdup(p);
907     appData.isUCI[i] = isUCI;
908     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
909     appData.hasOwnBookUCI[i] = hasBook;
910     if(!nickName[0]) useNick = FALSE;
911     if(useNick) ASSIGN(appData.pgnName[i], nickName);
912     if(addToList) {
913         int len;
914         char quote;
915         q = firstChessProgramNames;
916         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
917         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
918         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
919                         quote, p, quote, appData.directory[i], 
920                         useNick ? " -fn \"" : "",
921                         useNick ? nickName : "",
922                         useNick ? "\"" : "",
923                         v1 ? " -firstProtocolVersion 1" : "",
924                         hasBook ? "" : " -fNoOwnBookUCI",
925                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
926                         storeVariant ? " -variant " : "",
927                         storeVariant ? VariantName(gameInfo.variant) : "");
928         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
929         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
930         if(q)   free(q);
931     }
932     ReplaceEngine(cps, i);
933 }
934
935 void
936 InitTimeControls()
937 {
938     int matched, min, sec;
939     /*
940      * Parse timeControl resource
941      */
942     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
943                           appData.movesPerSession)) {
944         char buf[MSG_SIZ];
945         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
946         DisplayFatalError(buf, 0, 2);
947     }
948
949     /*
950      * Parse searchTime resource
951      */
952     if (*appData.searchTime != NULLCHAR) {
953         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
954         if (matched == 1) {
955             searchTime = min * 60;
956         } else if (matched == 2) {
957             searchTime = min * 60 + sec;
958         } else {
959             char buf[MSG_SIZ];
960             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
961             DisplayFatalError(buf, 0, 2);
962         }
963     }
964 }
965
966 void
967 InitBackEnd1()
968 {
969
970     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
971     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
972
973     GetTimeMark(&programStartTime);
974     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
975     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976
977     ClearProgramStats();
978     programStats.ok_to_send = 1;
979     programStats.seen_stat = 0;
980
981     /*
982      * Initialize game list
983      */
984     ListNew(&gameList);
985
986
987     /*
988      * Internet chess server status
989      */
990     if (appData.icsActive) {
991         appData.matchMode = FALSE;
992         appData.matchGames = 0;
993 #if ZIPPY
994         appData.noChessProgram = !appData.zippyPlay;
995 #else
996         appData.zippyPlay = FALSE;
997         appData.zippyTalk = FALSE;
998         appData.noChessProgram = TRUE;
999 #endif
1000         if (*appData.icsHelper != NULLCHAR) {
1001             appData.useTelnet = TRUE;
1002             appData.telnetProgram = appData.icsHelper;
1003         }
1004     } else {
1005         appData.zippyTalk = appData.zippyPlay = FALSE;
1006     }
1007
1008     /* [AS] Initialize pv info list [HGM] and game state */
1009     {
1010         int i, j;
1011
1012         for( i=0; i<=framePtr; i++ ) {
1013             pvInfoList[i].depth = -1;
1014             boards[i][EP_STATUS] = EP_NONE;
1015             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1016         }
1017     }
1018
1019     InitTimeControls();
1020
1021     /* [AS] Adjudication threshold */
1022     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1023
1024     InitEngine(&first, 0);
1025     InitEngine(&second, 1);
1026     CommonEngineInit();
1027
1028     pairing.which = "pairing"; // pairing engine
1029     pairing.pr = NoProc;
1030     pairing.isr = NULL;
1031     pairing.program = appData.pairingEngine;
1032     pairing.host = "localhost";
1033     pairing.dir = ".";
1034
1035     if (appData.icsActive) {
1036         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1037     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1038         appData.clockMode = FALSE;
1039         first.sendTime = second.sendTime = 0;
1040     }
1041
1042 #if ZIPPY
1043     /* Override some settings from environment variables, for backward
1044        compatibility.  Unfortunately it's not feasible to have the env
1045        vars just set defaults, at least in xboard.  Ugh.
1046     */
1047     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1048       ZippyInit();
1049     }
1050 #endif
1051
1052     if (!appData.icsActive) {
1053       char buf[MSG_SIZ];
1054       int len;
1055
1056       /* Check for variants that are supported only in ICS mode,
1057          or not at all.  Some that are accepted here nevertheless
1058          have bugs; see comments below.
1059       */
1060       VariantClass variant = StringToVariant(appData.variant);
1061       switch (variant) {
1062       case VariantBughouse:     /* need four players and two boards */
1063       case VariantKriegspiel:   /* need to hide pieces and move details */
1064         /* case VariantFischeRandom: (Fabien: moved below) */
1065         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1066         if( (len > MSG_SIZ) && appData.debugMode )
1067           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068
1069         DisplayFatalError(buf, 0, 2);
1070         return;
1071
1072       case VariantUnknown:
1073       case VariantLoadable:
1074       case Variant29:
1075       case Variant30:
1076       case Variant31:
1077       case Variant32:
1078       case Variant33:
1079       case Variant34:
1080       case Variant35:
1081       case Variant36:
1082       default:
1083         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1084         if( (len > MSG_SIZ) && appData.debugMode )
1085           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1086
1087         DisplayFatalError(buf, 0, 2);
1088         return;
1089
1090       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1091       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1092       case VariantGothic:     /* [HGM] should work */
1093       case VariantCapablanca: /* [HGM] should work */
1094       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1095       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1096       case VariantKnightmate: /* [HGM] should work */
1097       case VariantCylinder:   /* [HGM] untested */
1098       case VariantFalcon:     /* [HGM] untested */
1099       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1100                                  offboard interposition not understood */
1101       case VariantNormal:     /* definitely works! */
1102       case VariantWildCastle: /* pieces not automatically shuffled */
1103       case VariantNoCastle:   /* pieces not automatically shuffled */
1104       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1105       case VariantLosers:     /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantSuicide:    /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantGiveaway:   /* should work except for win condition,
1110                                  and doesn't know captures are mandatory */
1111       case VariantTwoKings:   /* should work */
1112       case VariantAtomic:     /* should work except for win condition */
1113       case Variant3Check:     /* should work except for win condition */
1114       case VariantShatranj:   /* should work except for all win conditions */
1115       case VariantMakruk:     /* should work except for daw countdown */
1116       case VariantBerolina:   /* might work if TestLegality is off */
1117       case VariantCapaRandom: /* should work */
1118       case VariantJanus:      /* should work */
1119       case VariantSuper:      /* experimental */
1120       case VariantGreat:      /* experimental, requires legality testing to be off */
1121       case VariantSChess:     /* S-Chess, should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len > MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len > MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 /*
1658  * Establish will establish a contact to a remote host.port.
1659  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1660  *  used to talk to the host.
1661  * Returns 0 if okay, error code if not.
1662  */
1663 int
1664 establish()
1665 {
1666     char buf[MSG_SIZ];
1667
1668     if (*appData.icsCommPort != NULLCHAR) {
1669         /* Talk to the host through a serial comm port */
1670         return OpenCommPort(appData.icsCommPort, &icsPR);
1671
1672     } else if (*appData.gateway != NULLCHAR) {
1673         if (*appData.remoteShell == NULLCHAR) {
1674             /* Use the rcmd protocol to run telnet program on a gateway host */
1675             snprintf(buf, sizeof(buf), "%s %s %s",
1676                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1677             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1678
1679         } else {
1680             /* Use the rsh program to run telnet program on a gateway host */
1681             if (*appData.remoteUser == NULLCHAR) {
1682                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1683                         appData.gateway, appData.telnetProgram,
1684                         appData.icsHost, appData.icsPort);
1685             } else {
1686                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1687                         appData.remoteShell, appData.gateway,
1688                         appData.remoteUser, appData.telnetProgram,
1689                         appData.icsHost, appData.icsPort);
1690             }
1691             return StartChildProcess(buf, "", &icsPR);
1692
1693         }
1694     } else if (appData.useTelnet) {
1695         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1696
1697     } else {
1698         /* TCP socket interface differs somewhat between
1699            Unix and NT; handle details in the front end.
1700            */
1701         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1702     }
1703 }
1704
1705 void EscapeExpand(char *p, char *q)
1706 {       // [HGM] initstring: routine to shape up string arguments
1707         while(*p++ = *q++) if(p[-1] == '\\')
1708             switch(*q++) {
1709                 case 'n': p[-1] = '\n'; break;
1710                 case 'r': p[-1] = '\r'; break;
1711                 case 't': p[-1] = '\t'; break;
1712                 case '\\': p[-1] = '\\'; break;
1713                 case 0: *p = 0; return;
1714                 default: p[-1] = q[-1]; break;
1715             }
1716 }
1717
1718 void
1719 show_bytes(fp, buf, count)
1720      FILE *fp;
1721      char *buf;
1722      int count;
1723 {
1724     while (count--) {
1725         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1726             fprintf(fp, "\\%03o", *buf & 0xff);
1727         } else {
1728             putc(*buf, fp);
1729         }
1730         buf++;
1731     }
1732     fflush(fp);
1733 }
1734
1735 /* Returns an errno value */
1736 int
1737 OutputMaybeTelnet(pr, message, count, outError)
1738      ProcRef pr;
1739      char *message;
1740      int count;
1741      int *outError;
1742 {
1743     char buf[8192], *p, *q, *buflim;
1744     int left, newcount, outcount;
1745
1746     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1747         *appData.gateway != NULLCHAR) {
1748         if (appData.debugMode) {
1749             fprintf(debugFP, ">ICS: ");
1750             show_bytes(debugFP, message, count);
1751             fprintf(debugFP, "\n");
1752         }
1753         return OutputToProcess(pr, message, count, outError);
1754     }
1755
1756     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1757     p = message;
1758     q = buf;
1759     left = count;
1760     newcount = 0;
1761     while (left) {
1762         if (q >= buflim) {
1763             if (appData.debugMode) {
1764                 fprintf(debugFP, ">ICS: ");
1765                 show_bytes(debugFP, buf, newcount);
1766                 fprintf(debugFP, "\n");
1767             }
1768             outcount = OutputToProcess(pr, buf, newcount, outError);
1769             if (outcount < newcount) return -1; /* to be sure */
1770             q = buf;
1771             newcount = 0;
1772         }
1773         if (*p == '\n') {
1774             *q++ = '\r';
1775             newcount++;
1776         } else if (((unsigned char) *p) == TN_IAC) {
1777             *q++ = (char) TN_IAC;
1778             newcount ++;
1779         }
1780         *q++ = *p++;
1781         newcount++;
1782         left--;
1783     }
1784     if (appData.debugMode) {
1785         fprintf(debugFP, ">ICS: ");
1786         show_bytes(debugFP, buf, newcount);
1787         fprintf(debugFP, "\n");
1788     }
1789     outcount = OutputToProcess(pr, buf, newcount, outError);
1790     if (outcount < newcount) return -1; /* to be sure */
1791     return count;
1792 }
1793
1794 void
1795 read_from_player(isr, closure, message, count, error)
1796      InputSourceRef isr;
1797      VOIDSTAR closure;
1798      char *message;
1799      int count;
1800      int error;
1801 {
1802     int outError, outCount;
1803     static int gotEof = 0;
1804
1805     /* Pass data read from player on to ICS */
1806     if (count > 0) {
1807         gotEof = 0;
1808         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1809         if (outCount < count) {
1810             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1811         }
1812     } else if (count < 0) {
1813         RemoveInputSource(isr);
1814         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1815     } else if (gotEof++ > 0) {
1816         RemoveInputSource(isr);
1817         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1818     }
1819 }
1820
1821 void
1822 KeepAlive()
1823 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1824     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1825     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1826     SendToICS("date\n");
1827     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1828 }
1829
1830 /* added routine for printf style output to ics */
1831 void ics_printf(char *format, ...)
1832 {
1833     char buffer[MSG_SIZ];
1834     va_list args;
1835
1836     va_start(args, format);
1837     vsnprintf(buffer, sizeof(buffer), format, args);
1838     buffer[sizeof(buffer)-1] = '\0';
1839     SendToICS(buffer);
1840     va_end(args);
1841 }
1842
1843 void
1844 SendToICS(s)
1845      char *s;
1846 {
1847     int count, outCount, outError;
1848
1849     if (icsPR == NULL) return;
1850
1851     count = strlen(s);
1852     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1853     if (outCount < count) {
1854         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1855     }
1856 }
1857
1858 /* This is used for sending logon scripts to the ICS. Sending
1859    without a delay causes problems when using timestamp on ICC
1860    (at least on my machine). */
1861 void
1862 SendToICSDelayed(s,msdelay)
1863      char *s;
1864      long msdelay;
1865 {
1866     int count, outCount, outError;
1867
1868     if (icsPR == NULL) return;
1869
1870     count = strlen(s);
1871     if (appData.debugMode) {
1872         fprintf(debugFP, ">ICS: ");
1873         show_bytes(debugFP, s, count);
1874         fprintf(debugFP, "\n");
1875     }
1876     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877                                       msdelay);
1878     if (outCount < count) {
1879         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880     }
1881 }
1882
1883
1884 /* Remove all highlighting escape sequences in s
1885    Also deletes any suffix starting with '('
1886    */
1887 char *
1888 StripHighlightAndTitle(s)
1889      char *s;
1890 {
1891     static char retbuf[MSG_SIZ];
1892     char *p = retbuf;
1893
1894     while (*s != NULLCHAR) {
1895         while (*s == '\033') {
1896             while (*s != NULLCHAR && !isalpha(*s)) s++;
1897             if (*s != NULLCHAR) s++;
1898         }
1899         while (*s != NULLCHAR && *s != '\033') {
1900             if (*s == '(' || *s == '[') {
1901                 *p = NULLCHAR;
1902                 return retbuf;
1903             }
1904             *p++ = *s++;
1905         }
1906     }
1907     *p = NULLCHAR;
1908     return retbuf;
1909 }
1910
1911 /* Remove all highlighting escape sequences in s */
1912 char *
1913 StripHighlight(s)
1914      char *s;
1915 {
1916     static char retbuf[MSG_SIZ];
1917     char *p = retbuf;
1918
1919     while (*s != NULLCHAR) {
1920         while (*s == '\033') {
1921             while (*s != NULLCHAR && !isalpha(*s)) s++;
1922             if (*s != NULLCHAR) s++;
1923         }
1924         while (*s != NULLCHAR && *s != '\033') {
1925             *p++ = *s++;
1926         }
1927     }
1928     *p = NULLCHAR;
1929     return retbuf;
1930 }
1931
1932 char *variantNames[] = VARIANT_NAMES;
1933 char *
1934 VariantName(v)
1935      VariantClass v;
1936 {
1937     return variantNames[v];
1938 }
1939
1940
1941 /* Identify a variant from the strings the chess servers use or the
1942    PGN Variant tag names we use. */
1943 VariantClass
1944 StringToVariant(e)
1945      char *e;
1946 {
1947     char *p;
1948     int wnum = -1;
1949     VariantClass v = VariantNormal;
1950     int i, found = FALSE;
1951     char buf[MSG_SIZ];
1952     int len;
1953
1954     if (!e) return v;
1955
1956     /* [HGM] skip over optional board-size prefixes */
1957     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1958         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1959         while( *e++ != '_');
1960     }
1961
1962     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1963         v = VariantNormal;
1964         found = TRUE;
1965     } else
1966     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1967       if (StrCaseStr(e, variantNames[i])) {
1968         v = (VariantClass) i;
1969         found = TRUE;
1970         break;
1971       }
1972     }
1973
1974     if (!found) {
1975       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1976           || StrCaseStr(e, "wild/fr")
1977           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1978         v = VariantFischeRandom;
1979       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1980                  (i = 1, p = StrCaseStr(e, "w"))) {
1981         p += i;
1982         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1983         if (isdigit(*p)) {
1984           wnum = atoi(p);
1985         } else {
1986           wnum = -1;
1987         }
1988         switch (wnum) {
1989         case 0: /* FICS only, actually */
1990         case 1:
1991           /* Castling legal even if K starts on d-file */
1992           v = VariantWildCastle;
1993           break;
1994         case 2:
1995         case 3:
1996         case 4:
1997           /* Castling illegal even if K & R happen to start in
1998              normal positions. */
1999           v = VariantNoCastle;
2000           break;
2001         case 5:
2002         case 7:
2003         case 8:
2004         case 10:
2005         case 11:
2006         case 12:
2007         case 13:
2008         case 14:
2009         case 15:
2010         case 18:
2011         case 19:
2012           /* Castling legal iff K & R start in normal positions */
2013           v = VariantNormal;
2014           break;
2015         case 6:
2016         case 20:
2017         case 21:
2018           /* Special wilds for position setup; unclear what to do here */
2019           v = VariantLoadable;
2020           break;
2021         case 9:
2022           /* Bizarre ICC game */
2023           v = VariantTwoKings;
2024           break;
2025         case 16:
2026           v = VariantKriegspiel;
2027           break;
2028         case 17:
2029           v = VariantLosers;
2030           break;
2031         case 22:
2032           v = VariantFischeRandom;
2033           break;
2034         case 23:
2035           v = VariantCrazyhouse;
2036           break;
2037         case 24:
2038           v = VariantBughouse;
2039           break;
2040         case 25:
2041           v = Variant3Check;
2042           break;
2043         case 26:
2044           /* Not quite the same as FICS suicide! */
2045           v = VariantGiveaway;
2046           break;
2047         case 27:
2048           v = VariantAtomic;
2049           break;
2050         case 28:
2051           v = VariantShatranj;
2052           break;
2053
2054         /* Temporary names for future ICC types.  The name *will* change in
2055            the next xboard/WinBoard release after ICC defines it. */
2056         case 29:
2057           v = Variant29;
2058           break;
2059         case 30:
2060           v = Variant30;
2061           break;
2062         case 31:
2063           v = Variant31;
2064           break;
2065         case 32:
2066           v = Variant32;
2067           break;
2068         case 33:
2069           v = Variant33;
2070           break;
2071         case 34:
2072           v = Variant34;
2073           break;
2074         case 35:
2075           v = Variant35;
2076           break;
2077         case 36:
2078           v = Variant36;
2079           break;
2080         case 37:
2081           v = VariantShogi;
2082           break;
2083         case 38:
2084           v = VariantXiangqi;
2085           break;
2086         case 39:
2087           v = VariantCourier;
2088           break;
2089         case 40:
2090           v = VariantGothic;
2091           break;
2092         case 41:
2093           v = VariantCapablanca;
2094           break;
2095         case 42:
2096           v = VariantKnightmate;
2097           break;
2098         case 43:
2099           v = VariantFairy;
2100           break;
2101         case 44:
2102           v = VariantCylinder;
2103           break;
2104         case 45:
2105           v = VariantFalcon;
2106           break;
2107         case 46:
2108           v = VariantCapaRandom;
2109           break;
2110         case 47:
2111           v = VariantBerolina;
2112           break;
2113         case 48:
2114           v = VariantJanus;
2115           break;
2116         case 49:
2117           v = VariantSuper;
2118           break;
2119         case 50:
2120           v = VariantGreat;
2121           break;
2122         case -1:
2123           /* Found "wild" or "w" in the string but no number;
2124              must assume it's normal chess. */
2125           v = VariantNormal;
2126           break;
2127         default:
2128           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2129           if( (len > MSG_SIZ) && appData.debugMode )
2130             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2131
2132           DisplayError(buf, 0);
2133           v = VariantUnknown;
2134           break;
2135         }
2136       }
2137     }
2138     if (appData.debugMode) {
2139       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2140               e, wnum, VariantName(v));
2141     }
2142     return v;
2143 }
2144
2145 static int leftover_start = 0, leftover_len = 0;
2146 char star_match[STAR_MATCH_N][MSG_SIZ];
2147
2148 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2149    advance *index beyond it, and set leftover_start to the new value of
2150    *index; else return FALSE.  If pattern contains the character '*', it
2151    matches any sequence of characters not containing '\r', '\n', or the
2152    character following the '*' (if any), and the matched sequence(s) are
2153    copied into star_match.
2154    */
2155 int
2156 looking_at(buf, index, pattern)
2157      char *buf;
2158      int *index;
2159      char *pattern;
2160 {
2161     char *bufp = &buf[*index], *patternp = pattern;
2162     int star_count = 0;
2163     char *matchp = star_match[0];
2164
2165     for (;;) {
2166         if (*patternp == NULLCHAR) {
2167             *index = leftover_start = bufp - buf;
2168             *matchp = NULLCHAR;
2169             return TRUE;
2170         }
2171         if (*bufp == NULLCHAR) return FALSE;
2172         if (*patternp == '*') {
2173             if (*bufp == *(patternp + 1)) {
2174                 *matchp = NULLCHAR;
2175                 matchp = star_match[++star_count];
2176                 patternp += 2;
2177                 bufp++;
2178                 continue;
2179             } else if (*bufp == '\n' || *bufp == '\r') {
2180                 patternp++;
2181                 if (*patternp == NULLCHAR)
2182                   continue;
2183                 else
2184                   return FALSE;
2185             } else {
2186                 *matchp++ = *bufp++;
2187                 continue;
2188             }
2189         }
2190         if (*patternp != *bufp) return FALSE;
2191         patternp++;
2192         bufp++;
2193     }
2194 }
2195
2196 void
2197 SendToPlayer(data, length)
2198      char *data;
2199      int length;
2200 {
2201     int error, outCount;
2202     outCount = OutputToProcess(NoProc, data, length, &error);
2203     if (outCount < length) {
2204         DisplayFatalError(_("Error writing to display"), error, 1);
2205     }
2206 }
2207
2208 void
2209 PackHolding(packed, holding)
2210      char packed[];
2211      char *holding;
2212 {
2213     char *p = holding;
2214     char *q = packed;
2215     int runlength = 0;
2216     int curr = 9999;
2217     do {
2218         if (*p == curr) {
2219             runlength++;
2220         } else {
2221             switch (runlength) {
2222               case 0:
2223                 break;
2224               case 1:
2225                 *q++ = curr;
2226                 break;
2227               case 2:
2228                 *q++ = curr;
2229                 *q++ = curr;
2230                 break;
2231               default:
2232                 sprintf(q, "%d", runlength);
2233                 while (*q) q++;
2234                 *q++ = curr;
2235                 break;
2236             }
2237             runlength = 1;
2238             curr = *p;
2239         }
2240     } while (*p++);
2241     *q = NULLCHAR;
2242 }
2243
2244 /* Telnet protocol requests from the front end */
2245 void
2246 TelnetRequest(ddww, option)
2247      unsigned char ddww, option;
2248 {
2249     unsigned char msg[3];
2250     int outCount, outError;
2251
2252     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2253
2254     if (appData.debugMode) {
2255         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2256         switch (ddww) {
2257           case TN_DO:
2258             ddwwStr = "DO";
2259             break;
2260           case TN_DONT:
2261             ddwwStr = "DONT";
2262             break;
2263           case TN_WILL:
2264             ddwwStr = "WILL";
2265             break;
2266           case TN_WONT:
2267             ddwwStr = "WONT";
2268             break;
2269           default:
2270             ddwwStr = buf1;
2271             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2272             break;
2273         }
2274         switch (option) {
2275           case TN_ECHO:
2276             optionStr = "ECHO";
2277             break;
2278           default:
2279             optionStr = buf2;
2280             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2281             break;
2282         }
2283         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2284     }
2285     msg[0] = TN_IAC;
2286     msg[1] = ddww;
2287     msg[2] = option;
2288     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2289     if (outCount < 3) {
2290         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2291     }
2292 }
2293
2294 void
2295 DoEcho()
2296 {
2297     if (!appData.icsActive) return;
2298     TelnetRequest(TN_DO, TN_ECHO);
2299 }
2300
2301 void
2302 DontEcho()
2303 {
2304     if (!appData.icsActive) return;
2305     TelnetRequest(TN_DONT, TN_ECHO);
2306 }
2307
2308 void
2309 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2310 {
2311     /* put the holdings sent to us by the server on the board holdings area */
2312     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2313     char p;
2314     ChessSquare piece;
2315
2316     if(gameInfo.holdingsWidth < 2)  return;
2317     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2318         return; // prevent overwriting by pre-board holdings
2319
2320     if( (int)lowestPiece >= BlackPawn ) {
2321         holdingsColumn = 0;
2322         countsColumn = 1;
2323         holdingsStartRow = BOARD_HEIGHT-1;
2324         direction = -1;
2325     } else {
2326         holdingsColumn = BOARD_WIDTH-1;
2327         countsColumn = BOARD_WIDTH-2;
2328         holdingsStartRow = 0;
2329         direction = 1;
2330     }
2331
2332     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2333         board[i][holdingsColumn] = EmptySquare;
2334         board[i][countsColumn]   = (ChessSquare) 0;
2335     }
2336     while( (p=*holdings++) != NULLCHAR ) {
2337         piece = CharToPiece( ToUpper(p) );
2338         if(piece == EmptySquare) continue;
2339         /*j = (int) piece - (int) WhitePawn;*/
2340         j = PieceToNumber(piece);
2341         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2342         if(j < 0) continue;               /* should not happen */
2343         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2344         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2345         board[holdingsStartRow+j*direction][countsColumn]++;
2346     }
2347 }
2348
2349
2350 void
2351 VariantSwitch(Board board, VariantClass newVariant)
2352 {
2353    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2354    static Board oldBoard;
2355
2356    startedFromPositionFile = FALSE;
2357    if(gameInfo.variant == newVariant) return;
2358
2359    /* [HGM] This routine is called each time an assignment is made to
2360     * gameInfo.variant during a game, to make sure the board sizes
2361     * are set to match the new variant. If that means adding or deleting
2362     * holdings, we shift the playing board accordingly
2363     * This kludge is needed because in ICS observe mode, we get boards
2364     * of an ongoing game without knowing the variant, and learn about the
2365     * latter only later. This can be because of the move list we requested,
2366     * in which case the game history is refilled from the beginning anyway,
2367     * but also when receiving holdings of a crazyhouse game. In the latter
2368     * case we want to add those holdings to the already received position.
2369     */
2370
2371
2372    if (appData.debugMode) {
2373      fprintf(debugFP, "Switch board from %s to %s\n",
2374              VariantName(gameInfo.variant), VariantName(newVariant));
2375      setbuf(debugFP, NULL);
2376    }
2377    shuffleOpenings = 0;       /* [HGM] shuffle */
2378    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2379    switch(newVariant)
2380      {
2381      case VariantShogi:
2382        newWidth = 9;  newHeight = 9;
2383        gameInfo.holdingsSize = 7;
2384      case VariantBughouse:
2385      case VariantCrazyhouse:
2386        newHoldingsWidth = 2; break;
2387      case VariantGreat:
2388        newWidth = 10;
2389      case VariantSuper:
2390        newHoldingsWidth = 2;
2391        gameInfo.holdingsSize = 8;
2392        break;
2393      case VariantGothic:
2394      case VariantCapablanca:
2395      case VariantCapaRandom:
2396        newWidth = 10;
2397      default:
2398        newHoldingsWidth = gameInfo.holdingsSize = 0;
2399      };
2400
2401    if(newWidth  != gameInfo.boardWidth  ||
2402       newHeight != gameInfo.boardHeight ||
2403       newHoldingsWidth != gameInfo.holdingsWidth ) {
2404
2405      /* shift position to new playing area, if needed */
2406      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2407        for(i=0; i<BOARD_HEIGHT; i++)
2408          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2409            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2410              board[i][j];
2411        for(i=0; i<newHeight; i++) {
2412          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2413          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2414        }
2415      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2416        for(i=0; i<BOARD_HEIGHT; i++)
2417          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2418            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419              board[i][j];
2420      }
2421      gameInfo.boardWidth  = newWidth;
2422      gameInfo.boardHeight = newHeight;
2423      gameInfo.holdingsWidth = newHoldingsWidth;
2424      gameInfo.variant = newVariant;
2425      InitDrawingSizes(-2, 0);
2426    } else gameInfo.variant = newVariant;
2427    CopyBoard(oldBoard, board);   // remember correctly formatted board
2428      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2429    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2430 }
2431
2432 static int loggedOn = FALSE;
2433
2434 /*-- Game start info cache: --*/
2435 int gs_gamenum;
2436 char gs_kind[MSG_SIZ];
2437 static char player1Name[128] = "";
2438 static char player2Name[128] = "";
2439 static char cont_seq[] = "\n\\   ";
2440 static int player1Rating = -1;
2441 static int player2Rating = -1;
2442 /*----------------------------*/
2443
2444 ColorClass curColor = ColorNormal;
2445 int suppressKibitz = 0;
2446
2447 // [HGM] seekgraph
2448 Boolean soughtPending = FALSE;
2449 Boolean seekGraphUp;
2450 #define MAX_SEEK_ADS 200
2451 #define SQUARE 0x80
2452 char *seekAdList[MAX_SEEK_ADS];
2453 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2454 float tcList[MAX_SEEK_ADS];
2455 char colorList[MAX_SEEK_ADS];
2456 int nrOfSeekAds = 0;
2457 int minRating = 1010, maxRating = 2800;
2458 int hMargin = 10, vMargin = 20, h, w;
2459 extern int squareSize, lineGap;
2460
2461 void
2462 PlotSeekAd(int i)
2463 {
2464         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2465         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2466         if(r < minRating+100 && r >=0 ) r = minRating+100;
2467         if(r > maxRating) r = maxRating;
2468         if(tc < 1.) tc = 1.;
2469         if(tc > 95.) tc = 95.;
2470         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2471         y = ((double)r - minRating)/(maxRating - minRating)
2472             * (h-vMargin-squareSize/8-1) + vMargin;
2473         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2474         if(strstr(seekAdList[i], " u ")) color = 1;
2475         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2476            !strstr(seekAdList[i], "bullet") &&
2477            !strstr(seekAdList[i], "blitz") &&
2478            !strstr(seekAdList[i], "standard") ) color = 2;
2479         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2480         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2481 }
2482
2483 void
2484 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2485 {
2486         char buf[MSG_SIZ], *ext = "";
2487         VariantClass v = StringToVariant(type);
2488         if(strstr(type, "wild")) {
2489             ext = type + 4; // append wild number
2490             if(v == VariantFischeRandom) type = "chess960"; else
2491             if(v == VariantLoadable) type = "setup"; else
2492             type = VariantName(v);
2493         }
2494         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2495         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2496             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2497             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2498             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2499             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2500             seekNrList[nrOfSeekAds] = nr;
2501             zList[nrOfSeekAds] = 0;
2502             seekAdList[nrOfSeekAds++] = StrSave(buf);
2503             if(plot) PlotSeekAd(nrOfSeekAds-1);
2504         }
2505 }
2506
2507 void
2508 EraseSeekDot(int i)
2509 {
2510     int x = xList[i], y = yList[i], d=squareSize/4, k;
2511     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2512     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2513     // now replot every dot that overlapped
2514     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2515         int xx = xList[k], yy = yList[k];
2516         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2517             DrawSeekDot(xx, yy, colorList[k]);
2518     }
2519 }
2520
2521 void
2522 RemoveSeekAd(int nr)
2523 {
2524         int i;
2525         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2526             EraseSeekDot(i);
2527             if(seekAdList[i]) free(seekAdList[i]);
2528             seekAdList[i] = seekAdList[--nrOfSeekAds];
2529             seekNrList[i] = seekNrList[nrOfSeekAds];
2530             ratingList[i] = ratingList[nrOfSeekAds];
2531             colorList[i]  = colorList[nrOfSeekAds];
2532             tcList[i] = tcList[nrOfSeekAds];
2533             xList[i]  = xList[nrOfSeekAds];
2534             yList[i]  = yList[nrOfSeekAds];
2535             zList[i]  = zList[nrOfSeekAds];
2536             seekAdList[nrOfSeekAds] = NULL;
2537             break;
2538         }
2539 }
2540
2541 Boolean
2542 MatchSoughtLine(char *line)
2543 {
2544     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2545     int nr, base, inc, u=0; char dummy;
2546
2547     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2548        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2549        (u=1) &&
2550        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2551         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2552         // match: compact and save the line
2553         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2554         return TRUE;
2555     }
2556     return FALSE;
2557 }
2558
2559 int
2560 DrawSeekGraph()
2561 {
2562     int i;
2563     if(!seekGraphUp) return FALSE;
2564     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2565     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2566
2567     DrawSeekBackground(0, 0, w, h);
2568     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2569     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2570     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2571         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2572         yy = h-1-yy;
2573         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2574         if(i%500 == 0) {
2575             char buf[MSG_SIZ];
2576             snprintf(buf, MSG_SIZ, "%d", i);
2577             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2578         }
2579     }
2580     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2581     for(i=1; i<100; i+=(i<10?1:5)) {
2582         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2583         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2584         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2585             char buf[MSG_SIZ];
2586             snprintf(buf, MSG_SIZ, "%d", i);
2587             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2588         }
2589     }
2590     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2591     return TRUE;
2592 }
2593
2594 int SeekGraphClick(ClickType click, int x, int y, int moving)
2595 {
2596     static int lastDown = 0, displayed = 0, lastSecond;
2597     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2598         if(click == Release || moving) return FALSE;
2599         nrOfSeekAds = 0;
2600         soughtPending = TRUE;
2601         SendToICS(ics_prefix);
2602         SendToICS("sought\n"); // should this be "sought all"?
2603     } else { // issue challenge based on clicked ad
2604         int dist = 10000; int i, closest = 0, second = 0;
2605         for(i=0; i<nrOfSeekAds; i++) {
2606             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2607             if(d < dist) { dist = d; closest = i; }
2608             second += (d - zList[i] < 120); // count in-range ads
2609             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2610         }
2611         if(dist < 120) {
2612             char buf[MSG_SIZ];
2613             second = (second > 1);
2614             if(displayed != closest || second != lastSecond) {
2615                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2616                 lastSecond = second; displayed = closest;
2617             }
2618             if(click == Press) {
2619                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2620                 lastDown = closest;
2621                 return TRUE;
2622             } // on press 'hit', only show info
2623             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2624             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2625             SendToICS(ics_prefix);
2626             SendToICS(buf);
2627             return TRUE; // let incoming board of started game pop down the graph
2628         } else if(click == Release) { // release 'miss' is ignored
2629             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2630             if(moving == 2) { // right up-click
2631                 nrOfSeekAds = 0; // refresh graph
2632                 soughtPending = TRUE;
2633                 SendToICS(ics_prefix);
2634                 SendToICS("sought\n"); // should this be "sought all"?
2635             }
2636             return TRUE;
2637         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2638         // press miss or release hit 'pop down' seek graph
2639         seekGraphUp = FALSE;
2640         DrawPosition(TRUE, NULL);
2641     }
2642     return TRUE;
2643 }
2644
2645 void
2646 read_from_ics(isr, closure, data, count, error)
2647      InputSourceRef isr;
2648      VOIDSTAR closure;
2649      char *data;
2650      int count;
2651      int error;
2652 {
2653 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2654 #define STARTED_NONE 0
2655 #define STARTED_MOVES 1
2656 #define STARTED_BOARD 2
2657 #define STARTED_OBSERVE 3
2658 #define STARTED_HOLDINGS 4
2659 #define STARTED_CHATTER 5
2660 #define STARTED_COMMENT 6
2661 #define STARTED_MOVES_NOHIDE 7
2662
2663     static int started = STARTED_NONE;
2664     static char parse[20000];
2665     static int parse_pos = 0;
2666     static char buf[BUF_SIZE + 1];
2667     static int firstTime = TRUE, intfSet = FALSE;
2668     static ColorClass prevColor = ColorNormal;
2669     static int savingComment = FALSE;
2670     static int cmatch = 0; // continuation sequence match
2671     char *bp;
2672     char str[MSG_SIZ];
2673     int i, oldi;
2674     int buf_len;
2675     int next_out;
2676     int tkind;
2677     int backup;    /* [DM] For zippy color lines */
2678     char *p;
2679     char talker[MSG_SIZ]; // [HGM] chat
2680     int channel;
2681
2682     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2683
2684     if (appData.debugMode) {
2685       if (!error) {
2686         fprintf(debugFP, "<ICS: ");
2687         show_bytes(debugFP, data, count);
2688         fprintf(debugFP, "\n");
2689       }
2690     }
2691
2692     if (appData.debugMode) { int f = forwardMostMove;
2693         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2694                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2695                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2696     }
2697     if (count > 0) {
2698         /* If last read ended with a partial line that we couldn't parse,
2699            prepend it to the new read and try again. */
2700         if (leftover_len > 0) {
2701             for (i=0; i<leftover_len; i++)
2702               buf[i] = buf[leftover_start + i];
2703         }
2704
2705     /* copy new characters into the buffer */
2706     bp = buf + leftover_len;
2707     buf_len=leftover_len;
2708     for (i=0; i<count; i++)
2709     {
2710         // ignore these
2711         if (data[i] == '\r')
2712             continue;
2713
2714         // join lines split by ICS?
2715         if (!appData.noJoin)
2716         {
2717             /*
2718                 Joining just consists of finding matches against the
2719                 continuation sequence, and discarding that sequence
2720                 if found instead of copying it.  So, until a match
2721                 fails, there's nothing to do since it might be the
2722                 complete sequence, and thus, something we don't want
2723                 copied.
2724             */
2725             if (data[i] == cont_seq[cmatch])
2726             {
2727                 cmatch++;
2728                 if (cmatch == strlen(cont_seq))
2729                 {
2730                     cmatch = 0; // complete match.  just reset the counter
2731
2732                     /*
2733                         it's possible for the ICS to not include the space
2734                         at the end of the last word, making our [correct]
2735                         join operation fuse two separate words.  the server
2736                         does this when the space occurs at the width setting.
2737                     */
2738                     if (!buf_len || buf[buf_len-1] != ' ')
2739                     {
2740                         *bp++ = ' ';
2741                         buf_len++;
2742                     }
2743                 }
2744                 continue;
2745             }
2746             else if (cmatch)
2747             {
2748                 /*
2749                     match failed, so we have to copy what matched before
2750                     falling through and copying this character.  In reality,
2751                     this will only ever be just the newline character, but
2752                     it doesn't hurt to be precise.
2753                 */
2754                 strncpy(bp, cont_seq, cmatch);
2755                 bp += cmatch;
2756                 buf_len += cmatch;
2757                 cmatch = 0;
2758             }
2759         }
2760
2761         // copy this char
2762         *bp++ = data[i];
2763         buf_len++;
2764     }
2765
2766         buf[buf_len] = NULLCHAR;
2767 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2768         next_out = 0;
2769         leftover_start = 0;
2770
2771         i = 0;
2772         while (i < buf_len) {
2773             /* Deal with part of the TELNET option negotiation
2774                protocol.  We refuse to do anything beyond the
2775                defaults, except that we allow the WILL ECHO option,
2776                which ICS uses to turn off password echoing when we are
2777                directly connected to it.  We reject this option
2778                if localLineEditing mode is on (always on in xboard)
2779                and we are talking to port 23, which might be a real
2780                telnet server that will try to keep WILL ECHO on permanently.
2781              */
2782             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2783                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2784                 unsigned char option;
2785                 oldi = i;
2786                 switch ((unsigned char) buf[++i]) {
2787                   case TN_WILL:
2788                     if (appData.debugMode)
2789                       fprintf(debugFP, "\n<WILL ");
2790                     switch (option = (unsigned char) buf[++i]) {
2791                       case TN_ECHO:
2792                         if (appData.debugMode)
2793                           fprintf(debugFP, "ECHO ");
2794                         /* Reply only if this is a change, according
2795                            to the protocol rules. */
2796                         if (remoteEchoOption) break;
2797                         if (appData.localLineEditing &&
2798                             atoi(appData.icsPort) == TN_PORT) {
2799                             TelnetRequest(TN_DONT, TN_ECHO);
2800                         } else {
2801                             EchoOff();
2802                             TelnetRequest(TN_DO, TN_ECHO);
2803                             remoteEchoOption = TRUE;
2804                         }
2805                         break;
2806                       default:
2807                         if (appData.debugMode)
2808                           fprintf(debugFP, "%d ", option);
2809                         /* Whatever this is, we don't want it. */
2810                         TelnetRequest(TN_DONT, option);
2811                         break;
2812                     }
2813                     break;
2814                   case TN_WONT:
2815                     if (appData.debugMode)
2816                       fprintf(debugFP, "\n<WONT ");
2817                     switch (option = (unsigned char) buf[++i]) {
2818                       case TN_ECHO:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "ECHO ");
2821                         /* Reply only if this is a change, according
2822                            to the protocol rules. */
2823                         if (!remoteEchoOption) break;
2824                         EchoOn();
2825                         TelnetRequest(TN_DONT, TN_ECHO);
2826                         remoteEchoOption = FALSE;
2827                         break;
2828                       default:
2829                         if (appData.debugMode)
2830                           fprintf(debugFP, "%d ", (unsigned char) option);
2831                         /* Whatever this is, it must already be turned
2832                            off, because we never agree to turn on
2833                            anything non-default, so according to the
2834                            protocol rules, we don't reply. */
2835                         break;
2836                     }
2837                     break;
2838                   case TN_DO:
2839                     if (appData.debugMode)
2840                       fprintf(debugFP, "\n<DO ");
2841                     switch (option = (unsigned char) buf[++i]) {
2842                       default:
2843                         /* Whatever this is, we refuse to do it. */
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", option);
2846                         TelnetRequest(TN_WONT, option);
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DONT:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DONT ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         /* Whatever this is, we are already not doing
2858                            it, because we never agree to do anything
2859                            non-default, so according to the protocol
2860                            rules, we don't reply. */
2861                         break;
2862                     }
2863                     break;
2864                   case TN_IAC:
2865                     if (appData.debugMode)
2866                       fprintf(debugFP, "\n<IAC ");
2867                     /* Doubled IAC; pass it through */
2868                     i--;
2869                     break;
2870                   default:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2873                     /* Drop all other telnet commands on the floor */
2874                     break;
2875                 }
2876                 if (oldi > next_out)
2877                   SendToPlayer(&buf[next_out], oldi - next_out);
2878                 if (++i > next_out)
2879                   next_out = i;
2880                 continue;
2881             }
2882
2883             /* OK, this at least will *usually* work */
2884             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2885                 loggedOn = TRUE;
2886             }
2887
2888             if (loggedOn && !intfSet) {
2889                 if (ics_type == ICS_ICC) {
2890                   snprintf(str, MSG_SIZ,
2891                           "/set-quietly interface %s\n/set-quietly style 12\n",
2892                           programVersion);
2893                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2894                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2895                 } else if (ics_type == ICS_CHESSNET) {
2896                   snprintf(str, MSG_SIZ, "/style 12\n");
2897                 } else {
2898                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2899                   strcat(str, programVersion);
2900                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2901                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2902                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2903 #ifdef WIN32
2904                   strcat(str, "$iset nohighlight 1\n");
2905 #endif
2906                   strcat(str, "$iset lock 1\n$style 12\n");
2907                 }
2908                 SendToICS(str);
2909                 NotifyFrontendLogin();
2910                 intfSet = TRUE;
2911             }
2912
2913             if (started == STARTED_COMMENT) {
2914                 /* Accumulate characters in comment */
2915                 parse[parse_pos++] = buf[i];
2916                 if (buf[i] == '\n') {
2917                     parse[parse_pos] = NULLCHAR;
2918                     if(chattingPartner>=0) {
2919                         char mess[MSG_SIZ];
2920                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2921                         OutputChatMessage(chattingPartner, mess);
2922                         chattingPartner = -1;
2923                         next_out = i+1; // [HGM] suppress printing in ICS window
2924                     } else
2925                     if(!suppressKibitz) // [HGM] kibitz
2926                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2927                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2928                         int nrDigit = 0, nrAlph = 0, j;
2929                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2930                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2931                         parse[parse_pos] = NULLCHAR;
2932                         // try to be smart: if it does not look like search info, it should go to
2933                         // ICS interaction window after all, not to engine-output window.
2934                         for(j=0; j<parse_pos; j++) { // count letters and digits
2935                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2936                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2937                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2938                         }
2939                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2940                             int depth=0; float score;
2941                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2942                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2943                                 pvInfoList[forwardMostMove-1].depth = depth;
2944                                 pvInfoList[forwardMostMove-1].score = 100*score;
2945                             }
2946                             OutputKibitz(suppressKibitz, parse);
2947                         } else {
2948                             char tmp[MSG_SIZ];
2949                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2950                             SendToPlayer(tmp, strlen(tmp));
2951                         }
2952                         next_out = i+1; // [HGM] suppress printing in ICS window
2953                     }
2954                     started = STARTED_NONE;
2955                 } else {
2956                     /* Don't match patterns against characters in comment */
2957                     i++;
2958                     continue;
2959                 }
2960             }
2961             if (started == STARTED_CHATTER) {
2962                 if (buf[i] != '\n') {
2963                     /* Don't match patterns against characters in chatter */
2964                     i++;
2965                     continue;
2966                 }
2967                 started = STARTED_NONE;
2968                 if(suppressKibitz) next_out = i+1;
2969             }
2970
2971             /* Kludge to deal with rcmd protocol */
2972             if (firstTime && looking_at(buf, &i, "\001*")) {
2973                 DisplayFatalError(&buf[1], 0, 1);
2974                 continue;
2975             } else {
2976                 firstTime = FALSE;
2977             }
2978
2979             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2980                 ics_type = ICS_ICC;
2981                 ics_prefix = "/";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2987                 ics_type = ICS_FICS;
2988                 ics_prefix = "$";
2989                 if (appData.debugMode)
2990                   fprintf(debugFP, "ics_type %d\n", ics_type);
2991                 continue;
2992             }
2993             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2994                 ics_type = ICS_CHESSNET;
2995                 ics_prefix = "/";
2996                 if (appData.debugMode)
2997                   fprintf(debugFP, "ics_type %d\n", ics_type);
2998                 continue;
2999             }
3000
3001             if (!loggedOn &&
3002                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3003                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3004                  looking_at(buf, &i, "will be \"*\""))) {
3005               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3006               continue;
3007             }
3008
3009             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3010               char buf[MSG_SIZ];
3011               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3012               DisplayIcsInteractionTitle(buf);
3013               have_set_title = TRUE;
3014             }
3015
3016             /* skip finger notes */
3017             if (started == STARTED_NONE &&
3018                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3019                  (buf[i] == '1' && buf[i+1] == '0')) &&
3020                 buf[i+2] == ':' && buf[i+3] == ' ') {
3021               started = STARTED_CHATTER;
3022               i += 3;
3023               continue;
3024             }
3025
3026             oldi = i;
3027             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3028             if(appData.seekGraph) {
3029                 if(soughtPending && MatchSoughtLine(buf+i)) {
3030                     i = strstr(buf+i, "rated") - buf;
3031                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3032                     next_out = leftover_start = i;
3033                     started = STARTED_CHATTER;
3034                     suppressKibitz = TRUE;
3035                     continue;
3036                 }
3037                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3038                         && looking_at(buf, &i, "* ads displayed")) {
3039                     soughtPending = FALSE;
3040                     seekGraphUp = TRUE;
3041                     DrawSeekGraph();
3042                     continue;
3043                 }
3044                 if(appData.autoRefresh) {
3045                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3046                         int s = (ics_type == ICS_ICC); // ICC format differs
3047                         if(seekGraphUp)
3048                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3049                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3050                         looking_at(buf, &i, "*% "); // eat prompt
3051                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3052                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053                         next_out = i; // suppress
3054                         continue;
3055                     }
3056                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3057                         char *p = star_match[0];
3058                         while(*p) {
3059                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3060                             while(*p && *p++ != ' '); // next
3061                         }
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3064                         next_out = i;
3065                         continue;
3066                     }
3067                 }
3068             }
3069
3070             /* skip formula vars */
3071             if (started == STARTED_NONE &&
3072                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3073               started = STARTED_CHATTER;
3074               i += 3;
3075               continue;
3076             }
3077
3078             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3079             if (appData.autoKibitz && started == STARTED_NONE &&
3080                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3081                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3082                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3083                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3084                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3085                         suppressKibitz = TRUE;
3086                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3087                         next_out = i;
3088                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3089                                 && (gameMode == IcsPlayingWhite)) ||
3090                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3091                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3092                             started = STARTED_CHATTER; // own kibitz we simply discard
3093                         else {
3094                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3095                             parse_pos = 0; parse[0] = NULLCHAR;
3096                             savingComment = TRUE;
3097                             suppressKibitz = gameMode != IcsObserving ? 2 :
3098                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3099                         }
3100                         continue;
3101                 } else
3102                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3103                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3104                          && atoi(star_match[0])) {
3105                     // suppress the acknowledgements of our own autoKibitz
3106                     char *p;
3107                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3109                     SendToPlayer(star_match[0], strlen(star_match[0]));
3110                     if(looking_at(buf, &i, "*% ")) // eat prompt
3111                         suppressKibitz = FALSE;
3112                     next_out = i;
3113                     continue;
3114                 }
3115             } // [HGM] kibitz: end of patch
3116
3117             // [HGM] chat: intercept tells by users for which we have an open chat window
3118             channel = -1;
3119             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3120                                            looking_at(buf, &i, "* whispers:") ||
3121                                            looking_at(buf, &i, "* kibitzes:") ||
3122                                            looking_at(buf, &i, "* shouts:") ||
3123                                            looking_at(buf, &i, "* c-shouts:") ||
3124                                            looking_at(buf, &i, "--> * ") ||
3125                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3126                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3127                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3128                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3129                 int p;
3130                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3131                 chattingPartner = -1;
3132
3133                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3134                 for(p=0; p<MAX_CHAT; p++) {
3135                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3136                     talker[0] = '['; strcat(talker, "] ");
3137                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3138                     chattingPartner = p; break;
3139                     }
3140                 } else
3141                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3142                 for(p=0; p<MAX_CHAT; p++) {
3143                     if(!strcmp("kibitzes", chatPartner[p])) {
3144                         talker[0] = '['; strcat(talker, "] ");
3145                         chattingPartner = p; break;
3146                     }
3147                 } else
3148                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3149                 for(p=0; p<MAX_CHAT; p++) {
3150                     if(!strcmp("whispers", chatPartner[p])) {
3151                         talker[0] = '['; strcat(talker, "] ");
3152                         chattingPartner = p; break;
3153                     }
3154                 } else
3155                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3156                   if(buf[i-8] == '-' && buf[i-3] == 't')
3157                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3158                     if(!strcmp("c-shouts", chatPartner[p])) {
3159                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3160                         chattingPartner = p; break;
3161                     }
3162                   }
3163                   if(chattingPartner < 0)
3164                   for(p=0; p<MAX_CHAT; p++) {
3165                     if(!strcmp("shouts", chatPartner[p])) {
3166                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3167                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3168                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3169                         chattingPartner = p; break;
3170                     }
3171                   }
3172                 }
3173                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3174                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3175                     talker[0] = 0; Colorize(ColorTell, FALSE);
3176                     chattingPartner = p; break;
3177                 }
3178                 if(chattingPartner<0) i = oldi; else {
3179                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3180                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3181                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3182                     started = STARTED_COMMENT;
3183                     parse_pos = 0; parse[0] = NULLCHAR;
3184                     savingComment = 3 + chattingPartner; // counts as TRUE
3185                     suppressKibitz = TRUE;
3186                     continue;
3187                 }
3188             } // [HGM] chat: end of patch
3189
3190           backup = i;
3191             if (appData.zippyTalk || appData.zippyPlay) {
3192                 /* [DM] Backup address for color zippy lines */
3193 #if ZIPPY
3194                if (loggedOn == TRUE)
3195                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3196                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3197 #endif
3198             } // [DM] 'else { ' deleted
3199                 if (
3200                     /* Regular tells and says */
3201                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3202                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3203                     looking_at(buf, &i, "* says: ") ||
3204                     /* Don't color "message" or "messages" output */
3205                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3206                     looking_at(buf, &i, "*. * at *:*: ") ||
3207                     looking_at(buf, &i, "--* (*:*): ") ||
3208                     /* Message notifications (same color as tells) */
3209                     looking_at(buf, &i, "* has left a message ") ||
3210                     looking_at(buf, &i, "* just sent you a message:\n") ||
3211                     /* Whispers and kibitzes */
3212                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3213                     looking_at(buf, &i, "* kibitzes: ") ||
3214                     /* Channel tells */
3215                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3216
3217                   if (tkind == 1 && strchr(star_match[0], ':')) {
3218                       /* Avoid "tells you:" spoofs in channels */
3219                      tkind = 3;
3220                   }
3221                   if (star_match[0][0] == NULLCHAR ||
3222                       strchr(star_match[0], ' ') ||
3223                       (tkind == 3 && strchr(star_match[1], ' '))) {
3224                     /* Reject bogus matches */
3225                     i = oldi;
3226                   } else {
3227                     if (appData.colorize) {
3228                       if (oldi > next_out) {
3229                         SendToPlayer(&buf[next_out], oldi - next_out);
3230                         next_out = oldi;
3231                       }
3232                       switch (tkind) {
3233                       case 1:
3234                         Colorize(ColorTell, FALSE);
3235                         curColor = ColorTell;
3236                         break;
3237                       case 2:
3238                         Colorize(ColorKibitz, FALSE);
3239                         curColor = ColorKibitz;
3240                         break;
3241                       case 3:
3242                         p = strrchr(star_match[1], '(');
3243                         if (p == NULL) {
3244                           p = star_match[1];
3245                         } else {
3246                           p++;
3247                         }
3248                         if (atoi(p) == 1) {
3249                           Colorize(ColorChannel1, FALSE);
3250                           curColor = ColorChannel1;
3251                         } else {
3252                           Colorize(ColorChannel, FALSE);
3253                           curColor = ColorChannel;
3254                         }
3255                         break;
3256                       case 5:
3257                         curColor = ColorNormal;
3258                         break;
3259                       }
3260                     }
3261                     if (started == STARTED_NONE && appData.autoComment &&
3262                         (gameMode == IcsObserving ||
3263                          gameMode == IcsPlayingWhite ||
3264                          gameMode == IcsPlayingBlack)) {
3265                       parse_pos = i - oldi;
3266                       memcpy(parse, &buf[oldi], parse_pos);
3267                       parse[parse_pos] = NULLCHAR;
3268                       started = STARTED_COMMENT;
3269                       savingComment = TRUE;
3270                     } else {
3271                       started = STARTED_CHATTER;
3272                       savingComment = FALSE;
3273                     }
3274                     loggedOn = TRUE;
3275                     continue;
3276                   }
3277                 }
3278
3279                 if (looking_at(buf, &i, "* s-shouts: ") ||
3280                     looking_at(buf, &i, "* c-shouts: ")) {
3281                     if (appData.colorize) {
3282                         if (oldi > next_out) {
3283                             SendToPlayer(&buf[next_out], oldi - next_out);
3284                             next_out = oldi;
3285                         }
3286                         Colorize(ColorSShout, FALSE);
3287                         curColor = ColorSShout;
3288                     }
3289                     loggedOn = TRUE;
3290                     started = STARTED_CHATTER;
3291                     continue;
3292                 }
3293
3294                 if (looking_at(buf, &i, "--->")) {
3295                     loggedOn = TRUE;
3296                     continue;
3297                 }
3298
3299                 if (looking_at(buf, &i, "* shouts: ") ||
3300                     looking_at(buf, &i, "--> ")) {
3301                     if (appData.colorize) {
3302                         if (oldi > next_out) {
3303                             SendToPlayer(&buf[next_out], oldi - next_out);
3304                             next_out = oldi;
3305                         }
3306                         Colorize(ColorShout, FALSE);
3307                         curColor = ColorShout;
3308                     }
3309                     loggedOn = TRUE;
3310                     started = STARTED_CHATTER;
3311                     continue;
3312                 }
3313
3314                 if (looking_at( buf, &i, "Challenge:")) {
3315                     if (appData.colorize) {
3316                         if (oldi > next_out) {
3317                             SendToPlayer(&buf[next_out], oldi - next_out);
3318                             next_out = oldi;
3319                         }
3320                         Colorize(ColorChallenge, FALSE);
3321                         curColor = ColorChallenge;
3322                     }
3323                     loggedOn = TRUE;
3324                     continue;
3325                 }
3326
3327                 if (looking_at(buf, &i, "* offers you") ||
3328                     looking_at(buf, &i, "* offers to be") ||
3329                     looking_at(buf, &i, "* would like to") ||
3330                     looking_at(buf, &i, "* requests to") ||
3331                     looking_at(buf, &i, "Your opponent offers") ||
3332                     looking_at(buf, &i, "Your opponent requests")) {
3333
3334                     if (appData.colorize) {
3335                         if (oldi > next_out) {
3336                             SendToPlayer(&buf[next_out], oldi - next_out);
3337                             next_out = oldi;
3338                         }
3339                         Colorize(ColorRequest, FALSE);
3340                         curColor = ColorRequest;
3341                     }
3342                     continue;
3343                 }
3344
3345                 if (looking_at(buf, &i, "* (*) seeking")) {
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorSeek, FALSE);
3352                         curColor = ColorSeek;
3353                     }
3354                     continue;
3355             }
3356
3357           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3358
3359             if (looking_at(buf, &i, "\\   ")) {
3360                 if (prevColor != ColorNormal) {
3361                     if (oldi > next_out) {
3362                         SendToPlayer(&buf[next_out], oldi - next_out);
3363                         next_out = oldi;
3364                     }
3365                     Colorize(prevColor, TRUE);
3366                     curColor = prevColor;
3367                 }
3368                 if (savingComment) {
3369                     parse_pos = i - oldi;
3370                     memcpy(parse, &buf[oldi], parse_pos);
3371                     parse[parse_pos] = NULLCHAR;
3372                     started = STARTED_COMMENT;
3373                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3374                         chattingPartner = savingComment - 3; // kludge to remember the box
3375                 } else {
3376                     started = STARTED_CHATTER;
3377                 }
3378                 continue;
3379             }
3380
3381             if (looking_at(buf, &i, "Black Strength :") ||
3382                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3383                 looking_at(buf, &i, "<10>") ||
3384                 looking_at(buf, &i, "#@#")) {
3385                 /* Wrong board style */
3386                 loggedOn = TRUE;
3387                 SendToICS(ics_prefix);
3388                 SendToICS("set style 12\n");
3389                 SendToICS(ics_prefix);
3390                 SendToICS("refresh\n");
3391                 continue;
3392             }
3393
3394             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3395                 ICSInitScript();
3396                 have_sent_ICS_logon = 1;
3397                 continue;
3398             }
3399
3400             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3401                 (looking_at(buf, &i, "\n<12> ") ||
3402                  looking_at(buf, &i, "<12> "))) {
3403                 loggedOn = TRUE;
3404                 if (oldi > next_out) {
3405                     SendToPlayer(&buf[next_out], oldi - next_out);
3406                 }
3407                 next_out = i;
3408                 started = STARTED_BOARD;
3409                 parse_pos = 0;
3410                 continue;
3411             }
3412
3413             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3414                 looking_at(buf, &i, "<b1> ")) {
3415                 if (oldi > next_out) {
3416                     SendToPlayer(&buf[next_out], oldi - next_out);
3417                 }
3418                 next_out = i;
3419                 started = STARTED_HOLDINGS;
3420                 parse_pos = 0;
3421                 continue;
3422             }
3423
3424             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3425                 loggedOn = TRUE;
3426                 /* Header for a move list -- first line */
3427
3428                 switch (ics_getting_history) {
3429                   case H_FALSE:
3430                     switch (gameMode) {
3431                       case IcsIdle:
3432                       case BeginningOfGame:
3433                         /* User typed "moves" or "oldmoves" while we
3434                            were idle.  Pretend we asked for these
3435                            moves and soak them up so user can step
3436                            through them and/or save them.
3437                            */
3438                         Reset(FALSE, TRUE);
3439                         gameMode = IcsObserving;
3440                         ModeHighlight();
3441                         ics_gamenum = -1;
3442                         ics_getting_history = H_GOT_UNREQ_HEADER;
3443                         break;
3444                       case EditGame: /*?*/
3445                       case EditPosition: /*?*/
3446                         /* Should above feature work in these modes too? */
3447                         /* For now it doesn't */
3448                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3449                         break;
3450                       default:
3451                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3452                         break;
3453                     }
3454                     break;
3455                   case H_REQUESTED:
3456                     /* Is this the right one? */
3457                     if (gameInfo.white && gameInfo.black &&
3458                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3459                         strcmp(gameInfo.black, star_match[2]) == 0) {
3460                         /* All is well */
3461                         ics_getting_history = H_GOT_REQ_HEADER;
3462                     }
3463                     break;
3464                   case H_GOT_REQ_HEADER:
3465                   case H_GOT_UNREQ_HEADER:
3466                   case H_GOT_UNWANTED_HEADER:
3467                   case H_GETTING_MOVES:
3468                     /* Should not happen */
3469                     DisplayError(_("Error gathering move list: two headers"), 0);
3470                     ics_getting_history = H_FALSE;
3471                     break;
3472                 }
3473
3474                 /* Save player ratings into gameInfo if needed */
3475                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3476                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3477                     (gameInfo.whiteRating == -1 ||
3478                      gameInfo.blackRating == -1)) {
3479
3480                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3481                     gameInfo.blackRating = string_to_rating(star_match[3]);
3482                     if (appData.debugMode)
3483                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3484                               gameInfo.whiteRating, gameInfo.blackRating);
3485                 }
3486                 continue;
3487             }
3488
3489             if (looking_at(buf, &i,
3490               "* * match, initial time: * minute*, increment: * second")) {
3491                 /* Header for a move list -- second line */
3492                 /* Initial board will follow if this is a wild game */
3493                 if (gameInfo.event != NULL) free(gameInfo.event);
3494                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3495                 gameInfo.event = StrSave(str);
3496                 /* [HGM] we switched variant. Translate boards if needed. */
3497                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i, "Move  ")) {
3502                 /* Beginning of a move list */
3503                 switch (ics_getting_history) {
3504                   case H_FALSE:
3505                     /* Normally should not happen */
3506                     /* Maybe user hit reset while we were parsing */
3507                     break;
3508                   case H_REQUESTED:
3509                     /* Happens if we are ignoring a move list that is not
3510                      * the one we just requested.  Common if the user
3511                      * tries to observe two games without turning off
3512                      * getMoveList */
3513                     break;
3514                   case H_GETTING_MOVES:
3515                     /* Should not happen */
3516                     DisplayError(_("Error gathering move list: nested"), 0);
3517                     ics_getting_history = H_FALSE;
3518                     break;
3519                   case H_GOT_REQ_HEADER:
3520                     ics_getting_history = H_GETTING_MOVES;
3521                     started = STARTED_MOVES;
3522                     parse_pos = 0;
3523                     if (oldi > next_out) {
3524                         SendToPlayer(&buf[next_out], oldi - next_out);
3525                     }
3526                     break;
3527                   case H_GOT_UNREQ_HEADER:
3528                     ics_getting_history = H_GETTING_MOVES;
3529                     started = STARTED_MOVES_NOHIDE;
3530                     parse_pos = 0;
3531                     break;
3532                   case H_GOT_UNWANTED_HEADER:
3533                     ics_getting_history = H_FALSE;
3534                     break;
3535                 }
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "% ") ||
3540                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3541                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3542                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3543                     soughtPending = FALSE;
3544                     seekGraphUp = TRUE;
3545                     DrawSeekGraph();
3546                 }
3547                 if(suppressKibitz) next_out = i;
3548                 savingComment = FALSE;
3549                 suppressKibitz = 0;
3550                 switch (started) {
3551                   case STARTED_MOVES:
3552                   case STARTED_MOVES_NOHIDE:
3553                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3554                     parse[parse_pos + i - oldi] = NULLCHAR;
3555                     ParseGameHistory(parse);
3556 #if ZIPPY
3557                     if (appData.zippyPlay && first.initDone) {
3558                         FeedMovesToProgram(&first, forwardMostMove);
3559                         if (gameMode == IcsPlayingWhite) {
3560                             if (WhiteOnMove(forwardMostMove)) {
3561                                 if (first.sendTime) {
3562                                   if (first.useColors) {
3563                                     SendToProgram("black\n", &first);
3564                                   }
3565                                   SendTimeRemaining(&first, TRUE);
3566                                 }
3567                                 if (first.useColors) {
3568                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3569                                 }
3570                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3571                                 first.maybeThinking = TRUE;
3572                             } else {
3573                                 if (first.usePlayother) {
3574                                   if (first.sendTime) {
3575                                     SendTimeRemaining(&first, TRUE);
3576                                   }
3577                                   SendToProgram("playother\n", &first);
3578                                   firstMove = FALSE;
3579                                 } else {
3580                                   firstMove = TRUE;
3581                                 }
3582                             }
3583                         } else if (gameMode == IcsPlayingBlack) {
3584                             if (!WhiteOnMove(forwardMostMove)) {
3585                                 if (first.sendTime) {
3586                                   if (first.useColors) {
3587                                     SendToProgram("white\n", &first);
3588                                   }
3589                                   SendTimeRemaining(&first, FALSE);
3590                                 }
3591                                 if (first.useColors) {
3592                                   SendToProgram("black\n", &first);
3593                                 }
3594                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3595                                 first.maybeThinking = TRUE;
3596                             } else {
3597                                 if (first.usePlayother) {
3598                                   if (first.sendTime) {
3599                                     SendTimeRemaining(&first, FALSE);
3600                                   }
3601                                   SendToProgram("playother\n", &first);
3602                                   firstMove = FALSE;
3603                                 } else {
3604                                   firstMove = TRUE;
3605                                 }
3606                             }
3607                         }
3608                     }
3609 #endif
3610                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3611                         /* Moves came from oldmoves or moves command
3612                            while we weren't doing anything else.
3613                            */
3614                         currentMove = forwardMostMove;
3615                         ClearHighlights();/*!!could figure this out*/
3616                         flipView = appData.flipView;
3617                         DrawPosition(TRUE, boards[currentMove]);
3618                         DisplayBothClocks();
3619                         snprintf(str, MSG_SIZ, "%s vs. %s",
3620                                 gameInfo.white, gameInfo.black);
3621                         DisplayTitle(str);
3622                         gameMode = IcsIdle;
3623                     } else {
3624                         /* Moves were history of an active game */
3625                         if (gameInfo.resultDetails != NULL) {
3626                             free(gameInfo.resultDetails);
3627                             gameInfo.resultDetails = NULL;
3628                         }
3629                     }
3630                     HistorySet(parseList, backwardMostMove,
3631                                forwardMostMove, currentMove-1);
3632                     DisplayMove(currentMove - 1);
3633                     if (started == STARTED_MOVES) next_out = i;
3634                     started = STARTED_NONE;
3635                     ics_getting_history = H_FALSE;
3636                     break;
3637
3638                   case STARTED_OBSERVE:
3639                     started = STARTED_NONE;
3640                     SendToICS(ics_prefix);
3641                     SendToICS("refresh\n");
3642                     break;
3643
3644                   default:
3645                     break;
3646                 }
3647                 if(bookHit) { // [HGM] book: simulate book reply
3648                     static char bookMove[MSG_SIZ]; // a bit generous?
3649
3650                     programStats.nodes = programStats.depth = programStats.time =
3651                     programStats.score = programStats.got_only_move = 0;
3652                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3653
3654                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3655                     strcat(bookMove, bookHit);
3656                     HandleMachineMove(bookMove, &first);
3657                 }
3658                 continue;
3659             }
3660
3661             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3662                  started == STARTED_HOLDINGS ||
3663                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3664                 /* Accumulate characters in move list or board */
3665                 parse[parse_pos++] = buf[i];
3666             }
3667
3668             /* Start of game messages.  Mostly we detect start of game
3669                when the first board image arrives.  On some versions
3670                of the ICS, though, we need to do a "refresh" after starting
3671                to observe in order to get the current board right away. */
3672             if (looking_at(buf, &i, "Adding game * to observation list")) {
3673                 started = STARTED_OBSERVE;
3674                 continue;
3675             }
3676
3677             /* Handle auto-observe */
3678             if (appData.autoObserve &&
3679                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3680                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3681                 char *player;
3682                 /* Choose the player that was highlighted, if any. */
3683                 if (star_match[0][0] == '\033' ||
3684                     star_match[1][0] != '\033') {
3685                     player = star_match[0];
3686                 } else {
3687                     player = star_match[2];
3688                 }
3689                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3690                         ics_prefix, StripHighlightAndTitle(player));
3691                 SendToICS(str);
3692
3693                 /* Save ratings from notify string */
3694                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3695                 player1Rating = string_to_rating(star_match[1]);
3696                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3697                 player2Rating = string_to_rating(star_match[3]);
3698
3699                 if (appData.debugMode)
3700                   fprintf(debugFP,
3701                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3702                           player1Name, player1Rating,
3703                           player2Name, player2Rating);
3704
3705                 continue;
3706             }
3707
3708             /* Deal with automatic examine mode after a game,
3709                and with IcsObserving -> IcsExamining transition */
3710             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3711                 looking_at(buf, &i, "has made you an examiner of game *")) {
3712
3713                 int gamenum = atoi(star_match[0]);
3714                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3715                     gamenum == ics_gamenum) {
3716                     /* We were already playing or observing this game;
3717                        no need to refetch history */
3718                     gameMode = IcsExamining;
3719                     if (pausing) {
3720                         pauseExamForwardMostMove = forwardMostMove;
3721                     } else if (currentMove < forwardMostMove) {
3722                         ForwardInner(forwardMostMove);
3723                     }
3724                 } else {
3725                     /* I don't think this case really can happen */
3726                     SendToICS(ics_prefix);
3727                     SendToICS("refresh\n");
3728                 }
3729                 continue;
3730             }
3731
3732             /* Error messages */
3733 //          if (ics_user_moved) {
3734             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3735                 if (looking_at(buf, &i, "Illegal move") ||
3736                     looking_at(buf, &i, "Not a legal move") ||
3737                     looking_at(buf, &i, "Your king is in check") ||
3738                     looking_at(buf, &i, "It isn't your turn") ||
3739                     looking_at(buf, &i, "It is not your move")) {
3740                     /* Illegal move */
3741                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3742                         currentMove = forwardMostMove-1;
3743                         DisplayMove(currentMove - 1); /* before DMError */
3744                         DrawPosition(FALSE, boards[currentMove]);
3745                         SwitchClocks(forwardMostMove-1); // [HGM] race
3746                         DisplayBothClocks();
3747                     }
3748                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3749                     ics_user_moved = 0;
3750                     continue;
3751                 }
3752             }
3753
3754             if (looking_at(buf, &i, "still have time") ||
3755                 looking_at(buf, &i, "not out of time") ||
3756                 looking_at(buf, &i, "either player is out of time") ||
3757                 looking_at(buf, &i, "has timeseal; checking")) {
3758                 /* We must have called his flag a little too soon */
3759                 whiteFlag = blackFlag = FALSE;
3760                 continue;
3761             }
3762
3763             if (looking_at(buf, &i, "added * seconds to") ||
3764                 looking_at(buf, &i, "seconds were added to")) {
3765                 /* Update the clocks */
3766                 SendToICS(ics_prefix);
3767                 SendToICS("refresh\n");
3768                 continue;
3769             }
3770
3771             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3772                 ics_clock_paused = TRUE;
3773                 StopClocks();
3774                 continue;
3775             }
3776
3777             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3778                 ics_clock_paused = FALSE;
3779                 StartClocks();
3780                 continue;
3781             }
3782
3783             /* Grab player ratings from the Creating: message.
3784                Note we have to check for the special case when
3785                the ICS inserts things like [white] or [black]. */
3786             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3787                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3788                 /* star_matches:
3789                    0    player 1 name (not necessarily white)
3790                    1    player 1 rating
3791                    2    empty, white, or black (IGNORED)
3792                    3    player 2 name (not necessarily black)
3793                    4    player 2 rating
3794
3795                    The names/ratings are sorted out when the game
3796                    actually starts (below).
3797                 */
3798                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3799                 player1Rating = string_to_rating(star_match[1]);
3800                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3801                 player2Rating = string_to_rating(star_match[4]);
3802
3803                 if (appData.debugMode)
3804                   fprintf(debugFP,
3805                           "Ratings from 'Creating:' %s %d, %s %d\n",
3806                           player1Name, player1Rating,
3807                           player2Name, player2Rating);
3808
3809                 continue;
3810             }
3811
3812             /* Improved generic start/end-of-game messages */
3813             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3814                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3815                 /* If tkind == 0: */
3816                 /* star_match[0] is the game number */
3817                 /*           [1] is the white player's name */
3818                 /*           [2] is the black player's name */
3819                 /* For end-of-game: */
3820                 /*           [3] is the reason for the game end */
3821                 /*           [4] is a PGN end game-token, preceded by " " */
3822                 /* For start-of-game: */
3823                 /*           [3] begins with "Creating" or "Continuing" */
3824                 /*           [4] is " *" or empty (don't care). */
3825                 int gamenum = atoi(star_match[0]);
3826                 char *whitename, *blackname, *why, *endtoken;
3827                 ChessMove endtype = EndOfFile;
3828
3829                 if (tkind == 0) {
3830                   whitename = star_match[1];
3831                   blackname = star_match[2];
3832                   why = star_match[3];
3833                   endtoken = star_match[4];
3834                 } else {
3835                   whitename = star_match[1];
3836                   blackname = star_match[3];
3837                   why = star_match[5];
3838                   endtoken = star_match[6];
3839                 }
3840
3841                 /* Game start messages */
3842                 if (strncmp(why, "Creating ", 9) == 0 ||
3843                     strncmp(why, "Continuing ", 11) == 0) {
3844                     gs_gamenum = gamenum;
3845                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3846                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3847 #if ZIPPY
3848                     if (appData.zippyPlay) {
3849                         ZippyGameStart(whitename, blackname);
3850                     }
3851 #endif /*ZIPPY*/
3852                     partnerBoardValid = FALSE; // [HGM] bughouse
3853                     continue;
3854                 }
3855
3856                 /* Game end messages */
3857                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3858                     ics_gamenum != gamenum) {
3859                     continue;
3860                 }
3861                 while (endtoken[0] == ' ') endtoken++;
3862                 switch (endtoken[0]) {
3863                   case '*':
3864                   default:
3865                     endtype = GameUnfinished;
3866                     break;
3867                   case '0':
3868                     endtype = BlackWins;
3869                     break;
3870                   case '1':
3871                     if (endtoken[1] == '/')
3872                       endtype = GameIsDrawn;
3873                     else
3874                       endtype = WhiteWins;
3875                     break;
3876                 }
3877                 GameEnds(endtype, why, GE_ICS);
3878 #if ZIPPY
3879                 if (appData.zippyPlay && first.initDone) {
3880                     ZippyGameEnd(endtype, why);
3881                     if (first.pr == NULL) {
3882                       /* Start the next process early so that we'll
3883                          be ready for the next challenge */
3884                       StartChessProgram(&first);
3885                     }
3886                     /* Send "new" early, in case this command takes
3887                        a long time to finish, so that we'll be ready
3888                        for the next challenge. */
3889                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3890                     Reset(TRUE, TRUE);
3891                 }
3892 #endif /*ZIPPY*/
3893                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3894                 continue;
3895             }
3896
3897             if (looking_at(buf, &i, "Removing game * from observation") ||
3898                 looking_at(buf, &i, "no longer observing game *") ||
3899                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3900                 if (gameMode == IcsObserving &&
3901                     atoi(star_match[0]) == ics_gamenum)
3902                   {
3903                       /* icsEngineAnalyze */
3904                       if (appData.icsEngineAnalyze) {
3905                             ExitAnalyzeMode();
3906                             ModeHighlight();
3907                       }
3908                       StopClocks();
3909                       gameMode = IcsIdle;
3910                       ics_gamenum = -1;
3911                       ics_user_moved = FALSE;
3912                   }
3913                 continue;
3914             }
3915
3916             if (looking_at(buf, &i, "no longer examining game *")) {
3917                 if (gameMode == IcsExamining &&
3918                     atoi(star_match[0]) == ics_gamenum)
3919                   {
3920                       gameMode = IcsIdle;
3921                       ics_gamenum = -1;
3922                       ics_user_moved = FALSE;
3923                   }
3924                 continue;
3925             }
3926
3927             /* Advance leftover_start past any newlines we find,
3928                so only partial lines can get reparsed */
3929             if (looking_at(buf, &i, "\n")) {
3930                 prevColor = curColor;
3931                 if (curColor != ColorNormal) {
3932                     if (oldi > next_out) {
3933                         SendToPlayer(&buf[next_out], oldi - next_out);
3934                         next_out = oldi;
3935                     }
3936                     Colorize(ColorNormal, FALSE);
3937                     curColor = ColorNormal;
3938                 }
3939                 if (started == STARTED_BOARD) {
3940                     started = STARTED_NONE;
3941                     parse[parse_pos] = NULLCHAR;
3942                     ParseBoard12(parse);
3943                     ics_user_moved = 0;
3944
3945                     /* Send premove here */
3946                     if (appData.premove) {
3947                       char str[MSG_SIZ];
3948                       if (currentMove == 0 &&
3949                           gameMode == IcsPlayingWhite &&
3950                           appData.premoveWhite) {
3951                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3952                         if (appData.debugMode)
3953                           fprintf(debugFP, "Sending premove:\n");
3954                         SendToICS(str);
3955                       } else if (currentMove == 1 &&
3956                                  gameMode == IcsPlayingBlack &&
3957                                  appData.premoveBlack) {
3958                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3959                         if (appData.debugMode)
3960                           fprintf(debugFP, "Sending premove:\n");
3961                         SendToICS(str);
3962                       } else if (gotPremove) {
3963                         gotPremove = 0;
3964                         ClearPremoveHighlights();
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                           UserMoveEvent(premoveFromX, premoveFromY,
3968                                         premoveToX, premoveToY,
3969                                         premovePromoChar);
3970                       }
3971                     }
3972
3973                     /* Usually suppress following prompt */
3974                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3975                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3976                         if (looking_at(buf, &i, "*% ")) {
3977                             savingComment = FALSE;
3978                             suppressKibitz = 0;
3979                         }
3980                     }
3981                     next_out = i;
3982                 } else if (started == STARTED_HOLDINGS) {
3983                     int gamenum;
3984                     char new_piece[MSG_SIZ];
3985                     started = STARTED_NONE;
3986                     parse[parse_pos] = NULLCHAR;
3987                     if (appData.debugMode)
3988                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3989                                                         parse, currentMove);
3990                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3991                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3992                         if (gameInfo.variant == VariantNormal) {
3993                           /* [HGM] We seem to switch variant during a game!
3994                            * Presumably no holdings were displayed, so we have
3995                            * to move the position two files to the right to
3996                            * create room for them!
3997                            */
3998                           VariantClass newVariant;
3999                           switch(gameInfo.boardWidth) { // base guess on board width
4000                                 case 9:  newVariant = VariantShogi; break;
4001                                 case 10: newVariant = VariantGreat; break;
4002                                 default: newVariant = VariantCrazyhouse; break;
4003                           }
4004                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4005                           /* Get a move list just to see the header, which
4006                              will tell us whether this is really bug or zh */
4007                           if (ics_getting_history == H_FALSE) {
4008                             ics_getting_history = H_REQUESTED;
4009                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4010                             SendToICS(str);
4011                           }
4012                         }
4013                         new_piece[0] = NULLCHAR;
4014                         sscanf(parse, "game %d white [%s black [%s <- %s",
4015                                &gamenum, white_holding, black_holding,
4016                                new_piece);
4017                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4018                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4019                         /* [HGM] copy holdings to board holdings area */
4020                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4021                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4022                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4023 #if ZIPPY
4024                         if (appData.zippyPlay && first.initDone) {
4025                             ZippyHoldings(white_holding, black_holding,
4026                                           new_piece);
4027                         }
4028 #endif /*ZIPPY*/
4029                         if (tinyLayout || smallLayout) {
4030                             char wh[16], bh[16];
4031                             PackHolding(wh, white_holding);
4032                             PackHolding(bh, black_holding);
4033                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4034                                     gameInfo.white, gameInfo.black);
4035                         } else {
4036                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4037                                     gameInfo.white, white_holding,
4038                                     gameInfo.black, black_holding);
4039                         }
4040                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4041                         DrawPosition(FALSE, boards[currentMove]);
4042                         DisplayTitle(str);
4043                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4044                         sscanf(parse, "game %d white [%s black [%s <- %s",
4045                                &gamenum, white_holding, black_holding,
4046                                new_piece);
4047                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4048                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4049                         /* [HGM] copy holdings to partner-board holdings area */
4050                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4051                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4052                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4053                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4054                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4055                       }
4056                     }
4057                     /* Suppress following prompt */
4058                     if (looking_at(buf, &i, "*% ")) {
4059                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4060                         savingComment = FALSE;
4061                         suppressKibitz = 0;
4062                     }
4063                     next_out = i;
4064                 }
4065                 continue;
4066             }
4067
4068             i++;                /* skip unparsed character and loop back */
4069         }
4070
4071         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4072 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4073 //          SendToPlayer(&buf[next_out], i - next_out);
4074             started != STARTED_HOLDINGS && leftover_start > next_out) {
4075             SendToPlayer(&buf[next_out], leftover_start - next_out);
4076             next_out = i;
4077         }
4078
4079         leftover_len = buf_len - leftover_start;
4080         /* if buffer ends with something we couldn't parse,
4081            reparse it after appending the next read */
4082
4083     } else if (count == 0) {
4084         RemoveInputSource(isr);
4085         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4086     } else {
4087         DisplayFatalError(_("Error reading from ICS"), error, 1);
4088     }
4089 }
4090
4091
4092 /* Board style 12 looks like this:
4093
4094    <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
4095
4096  * The "<12> " is stripped before it gets to this routine.  The two
4097  * trailing 0's (flip state and clock ticking) are later addition, and
4098  * some chess servers may not have them, or may have only the first.
4099  * Additional trailing fields may be added in the future.
4100  */
4101
4102 #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"
4103
4104 #define RELATION_OBSERVING_PLAYED    0
4105 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4106 #define RELATION_PLAYING_MYMOVE      1
4107 #define RELATION_PLAYING_NOTMYMOVE  -1
4108 #define RELATION_EXAMINING           2
4109 #define RELATION_ISOLATED_BOARD     -3
4110 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4111
4112 void
4113 ParseBoard12(string)
4114      char *string;
4115 {
4116     GameMode newGameMode;
4117     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4118     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4119     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4120     char to_play, board_chars[200];
4121     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4122     char black[32], white[32];
4123     Board board;
4124     int prevMove = currentMove;
4125     int ticking = 2;
4126     ChessMove moveType;
4127     int fromX, fromY, toX, toY;
4128     char promoChar;
4129     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4130     char *bookHit = NULL; // [HGM] book
4131     Boolean weird = FALSE, reqFlag = FALSE;
4132
4133     fromX = fromY = toX = toY = -1;
4134
4135     newGame = FALSE;
4136
4137     if (appData.debugMode)
4138       fprintf(debugFP, _("Parsing board: %s\n"), string);
4139
4140     move_str[0] = NULLCHAR;
4141     elapsed_time[0] = NULLCHAR;
4142     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4143         int  i = 0, j;
4144         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4145             if(string[i] == ' ') { ranks++; files = 0; }
4146             else files++;
4147             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4148             i++;
4149         }
4150         for(j = 0; j <i; j++) board_chars[j] = string[j];
4151         board_chars[i] = '\0';
4152         string += i + 1;
4153     }
4154     n = sscanf(string, PATTERN, &to_play, &double_push,
4155                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4156                &gamenum, white, black, &relation, &basetime, &increment,
4157                &white_stren, &black_stren, &white_time, &black_time,
4158                &moveNum, str, elapsed_time, move_str, &ics_flip,
4159                &ticking);
4160
4161     if (n < 21) {
4162         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4163         DisplayError(str, 0);
4164         return;
4165     }
4166
4167     /* Convert the move number to internal form */
4168     moveNum = (moveNum - 1) * 2;
4169     if (to_play == 'B') moveNum++;
4170     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4171       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4172                         0, 1);
4173       return;
4174     }
4175
4176     switch (relation) {
4177       case RELATION_OBSERVING_PLAYED:
4178       case RELATION_OBSERVING_STATIC:
4179         if (gamenum == -1) {
4180             /* Old ICC buglet */
4181             relation = RELATION_OBSERVING_STATIC;
4182         }
4183         newGameMode = IcsObserving;
4184         break;
4185       case RELATION_PLAYING_MYMOVE:
4186       case RELATION_PLAYING_NOTMYMOVE:
4187         newGameMode =
4188           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4189             IcsPlayingWhite : IcsPlayingBlack;
4190         break;
4191       case RELATION_EXAMINING:
4192         newGameMode = IcsExamining;
4193         break;
4194       case RELATION_ISOLATED_BOARD:
4195       default:
4196         /* Just display this board.  If user was doing something else,
4197            we will forget about it until the next board comes. */
4198         newGameMode = IcsIdle;
4199         break;
4200       case RELATION_STARTING_POSITION:
4201         newGameMode = gameMode;
4202         break;
4203     }
4204
4205     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4206          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4207       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4208       char *toSqr;
4209       for (k = 0; k < ranks; k++) {
4210         for (j = 0; j < files; j++)
4211           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4212         if(gameInfo.holdingsWidth > 1) {
4213              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4214              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4215         }
4216       }
4217       CopyBoard(partnerBoard, board);
4218       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4219         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4220         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4221       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4222       if(toSqr = strchr(str, '-')) {
4223         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4224         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4225       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4226       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4227       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4228       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4229       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4230       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4231                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4232       DisplayMessage(partnerStatus, "");
4233         partnerBoardValid = TRUE;
4234       return;
4235     }
4236
4237     /* Modify behavior for initial board display on move listing
4238        of wild games.
4239        */
4240     switch (ics_getting_history) {
4241       case H_FALSE:
4242       case H_REQUESTED:
4243         break;
4244       case H_GOT_REQ_HEADER:
4245       case H_GOT_UNREQ_HEADER:
4246         /* This is the initial position of the current game */
4247         gamenum = ics_gamenum;
4248         moveNum = 0;            /* old ICS bug workaround */
4249         if (to_play == 'B') {
4250           startedFromSetupPosition = TRUE;
4251           blackPlaysFirst = TRUE;
4252           moveNum = 1;
4253           if (forwardMostMove == 0) forwardMostMove = 1;
4254           if (backwardMostMove == 0) backwardMostMove = 1;
4255           if (currentMove == 0) currentMove = 1;
4256         }
4257         newGameMode = gameMode;
4258         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4259         break;
4260       case H_GOT_UNWANTED_HEADER:
4261         /* This is an initial board that we don't want */
4262         return;
4263       case H_GETTING_MOVES:
4264         /* Should not happen */
4265         DisplayError(_("Error gathering move list: extra board"), 0);
4266         ics_getting_history = H_FALSE;
4267         return;
4268     }
4269
4270    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4271                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4272      /* [HGM] We seem to have switched variant unexpectedly
4273       * Try to guess new variant from board size
4274       */
4275           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4276           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4277           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4278           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4279           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4280           if(!weird) newVariant = VariantNormal;
4281           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4282           /* Get a move list just to see the header, which
4283              will tell us whether this is really bug or zh */
4284           if (ics_getting_history == H_FALSE) {
4285             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4286             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4287             SendToICS(str);
4288           }
4289     }
4290
4291     /* Take action if this is the first board of a new game, or of a
4292        different game than is currently being displayed.  */
4293     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4294         relation == RELATION_ISOLATED_BOARD) {
4295
4296         /* Forget the old game and get the history (if any) of the new one */
4297         if (gameMode != BeginningOfGame) {
4298           Reset(TRUE, TRUE);
4299         }
4300         newGame = TRUE;
4301         if (appData.autoRaiseBoard) BoardToTop();
4302         prevMove = -3;
4303         if (gamenum == -1) {
4304             newGameMode = IcsIdle;
4305         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4306                    appData.getMoveList && !reqFlag) {
4307             /* Need to get game history */
4308             ics_getting_history = H_REQUESTED;
4309             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4310             SendToICS(str);
4311         }
4312
4313         /* Initially flip the board to have black on the bottom if playing
4314            black or if the ICS flip flag is set, but let the user change
4315            it with the Flip View button. */
4316         flipView = appData.autoFlipView ?
4317           (newGameMode == IcsPlayingBlack) || ics_flip :
4318           appData.flipView;
4319
4320         /* Done with values from previous mode; copy in new ones */
4321         gameMode = newGameMode;
4322         ModeHighlight();
4323         ics_gamenum = gamenum;
4324         if (gamenum == gs_gamenum) {
4325             int klen = strlen(gs_kind);
4326             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4327             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4328             gameInfo.event = StrSave(str);
4329         } else {
4330             gameInfo.event = StrSave("ICS game");
4331         }
4332         gameInfo.site = StrSave(appData.icsHost);
4333         gameInfo.date = PGNDate();
4334         gameInfo.round = StrSave("-");
4335         gameInfo.white = StrSave(white);
4336         gameInfo.black = StrSave(black);
4337         timeControl = basetime * 60 * 1000;
4338         timeControl_2 = 0;
4339         timeIncrement = increment * 1000;
4340         movesPerSession = 0;
4341         gameInfo.timeControl = TimeControlTagValue();
4342         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4343   if (appData.debugMode) {
4344     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4345     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4346     setbuf(debugFP, NULL);
4347   }
4348
4349         gameInfo.outOfBook = NULL;
4350
4351         /* Do we have the ratings? */
4352         if (strcmp(player1Name, white) == 0 &&
4353             strcmp(player2Name, black) == 0) {
4354             if (appData.debugMode)
4355               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4356                       player1Rating, player2Rating);
4357             gameInfo.whiteRating = player1Rating;
4358             gameInfo.blackRating = player2Rating;
4359         } else if (strcmp(player2Name, white) == 0 &&
4360                    strcmp(player1Name, black) == 0) {
4361             if (appData.debugMode)
4362               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4363                       player2Rating, player1Rating);
4364             gameInfo.whiteRating = player2Rating;
4365             gameInfo.blackRating = player1Rating;
4366         }
4367         player1Name[0] = player2Name[0] = NULLCHAR;
4368
4369         /* Silence shouts if requested */
4370         if (appData.quietPlay &&
4371             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4372             SendToICS(ics_prefix);
4373             SendToICS("set shout 0\n");
4374         }
4375     }
4376
4377     /* Deal with midgame name changes */
4378     if (!newGame) {
4379         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4380             if (gameInfo.white) free(gameInfo.white);
4381             gameInfo.white = StrSave(white);
4382         }
4383         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4384             if (gameInfo.black) free(gameInfo.black);
4385             gameInfo.black = StrSave(black);
4386         }
4387     }
4388
4389     /* Throw away game result if anything actually changes in examine mode */
4390     if (gameMode == IcsExamining && !newGame) {
4391         gameInfo.result = GameUnfinished;
4392         if (gameInfo.resultDetails != NULL) {
4393             free(gameInfo.resultDetails);
4394             gameInfo.resultDetails = NULL;
4395         }
4396     }
4397
4398     /* In pausing && IcsExamining mode, we ignore boards coming
4399        in if they are in a different variation than we are. */
4400     if (pauseExamInvalid) return;
4401     if (pausing && gameMode == IcsExamining) {
4402         if (moveNum <= pauseExamForwardMostMove) {
4403             pauseExamInvalid = TRUE;
4404             forwardMostMove = pauseExamForwardMostMove;
4405             return;
4406         }
4407     }
4408
4409   if (appData.debugMode) {
4410     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4411   }
4412     /* Parse the board */
4413     for (k = 0; k < ranks; k++) {
4414       for (j = 0; j < files; j++)
4415         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4416       if(gameInfo.holdingsWidth > 1) {
4417            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4418            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4419       }
4420     }
4421     CopyBoard(boards[moveNum], board);
4422     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4423     if (moveNum == 0) {
4424         startedFromSetupPosition =
4425           !CompareBoards(board, initialPosition);
4426         if(startedFromSetupPosition)
4427             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4428     }
4429
4430     /* [HGM] Set castling rights. Take the outermost Rooks,
4431        to make it also work for FRC opening positions. Note that board12
4432        is really defective for later FRC positions, as it has no way to
4433        indicate which Rook can castle if they are on the same side of King.
4434        For the initial position we grant rights to the outermost Rooks,
4435        and remember thos rights, and we then copy them on positions
4436        later in an FRC game. This means WB might not recognize castlings with
4437        Rooks that have moved back to their original position as illegal,
4438        but in ICS mode that is not its job anyway.
4439     */
4440     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4441     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4442
4443         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4444             if(board[0][i] == WhiteRook) j = i;
4445         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4446         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4447             if(board[0][i] == WhiteRook) j = i;
4448         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4449         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4450             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4451         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4452         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4453             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4454         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4455
4456         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4457         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4458             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4459         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4460             if(board[BOARD_HEIGHT-1][k] == bKing)
4461                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4462         if(gameInfo.variant == VariantTwoKings) {
4463             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4464             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4465             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4466         }
4467     } else { int r;
4468         r = boards[moveNum][CASTLING][0] = initialRights[0];
4469         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4470         r = boards[moveNum][CASTLING][1] = initialRights[1];
4471         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4472         r = boards[moveNum][CASTLING][3] = initialRights[3];
4473         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4474         r = boards[moveNum][CASTLING][4] = initialRights[4];
4475         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4476         /* wildcastle kludge: always assume King has rights */
4477         r = boards[moveNum][CASTLING][2] = initialRights[2];
4478         r = boards[moveNum][CASTLING][5] = initialRights[5];
4479     }
4480     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4481     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4482
4483
4484     if (ics_getting_history == H_GOT_REQ_HEADER ||
4485         ics_getting_history == H_GOT_UNREQ_HEADER) {
4486         /* This was an initial position from a move list, not
4487            the current position */
4488         return;
4489     }
4490
4491     /* Update currentMove and known move number limits */
4492     newMove = newGame || moveNum > forwardMostMove;
4493
4494     if (newGame) {
4495         forwardMostMove = backwardMostMove = currentMove = moveNum;
4496         if (gameMode == IcsExamining && moveNum == 0) {
4497           /* Workaround for ICS limitation: we are not told the wild
4498              type when starting to examine a game.  But if we ask for
4499              the move list, the move list header will tell us */
4500             ics_getting_history = H_REQUESTED;
4501             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4502             SendToICS(str);
4503         }
4504     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4505                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4506 #if ZIPPY
4507         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4508         /* [HGM] applied this also to an engine that is silently watching        */
4509         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4510             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4511             gameInfo.variant == currentlyInitializedVariant) {
4512           takeback = forwardMostMove - moveNum;
4513           for (i = 0; i < takeback; i++) {
4514             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4515             SendToProgram("undo\n", &first);
4516           }
4517         }
4518 #endif
4519
4520         forwardMostMove = moveNum;
4521         if (!pausing || currentMove > forwardMostMove)
4522           currentMove = forwardMostMove;
4523     } else {
4524         /* New part of history that is not contiguous with old part */
4525         if (pausing && gameMode == IcsExamining) {
4526             pauseExamInvalid = TRUE;
4527             forwardMostMove = pauseExamForwardMostMove;
4528             return;
4529         }
4530         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4531 #if ZIPPY
4532             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4533                 // [HGM] when we will receive the move list we now request, it will be
4534                 // fed to the engine from the first move on. So if the engine is not
4535                 // in the initial position now, bring it there.
4536                 InitChessProgram(&first, 0);
4537             }
4538 #endif
4539             ics_getting_history = H_REQUESTED;
4540             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4541             SendToICS(str);
4542         }
4543         forwardMostMove = backwardMostMove = currentMove = moveNum;
4544     }
4545
4546     /* Update the clocks */
4547     if (strchr(elapsed_time, '.')) {
4548       /* Time is in ms */
4549       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4550       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4551     } else {
4552       /* Time is in seconds */
4553       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4554       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4555     }
4556
4557
4558 #if ZIPPY
4559     if (appData.zippyPlay && newGame &&
4560         gameMode != IcsObserving && gameMode != IcsIdle &&
4561         gameMode != IcsExamining)
4562       ZippyFirstBoard(moveNum, basetime, increment);
4563 #endif
4564
4565     /* Put the move on the move list, first converting
4566        to canonical algebraic form. */
4567     if (moveNum > 0) {
4568   if (appData.debugMode) {
4569     if (appData.debugMode) { int f = forwardMostMove;
4570         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4571                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4572                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4573     }
4574     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4575     fprintf(debugFP, "moveNum = %d\n", moveNum);
4576     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4577     setbuf(debugFP, NULL);
4578   }
4579         if (moveNum <= backwardMostMove) {
4580             /* We don't know what the board looked like before
4581                this move.  Punt. */
4582           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4583             strcat(parseList[moveNum - 1], " ");
4584             strcat(parseList[moveNum - 1], elapsed_time);
4585             moveList[moveNum - 1][0] = NULLCHAR;
4586         } else if (strcmp(move_str, "none") == 0) {
4587             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4588             /* Again, we don't know what the board looked like;
4589                this is really the start of the game. */
4590             parseList[moveNum - 1][0] = NULLCHAR;
4591             moveList[moveNum - 1][0] = NULLCHAR;
4592             backwardMostMove = moveNum;
4593             startedFromSetupPosition = TRUE;
4594             fromX = fromY = toX = toY = -1;
4595         } else {
4596           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4597           //                 So we parse the long-algebraic move string in stead of the SAN move
4598           int valid; char buf[MSG_SIZ], *prom;
4599
4600           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4601                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4602           // str looks something like "Q/a1-a2"; kill the slash
4603           if(str[1] == '/')
4604             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4605           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4606           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4607                 strcat(buf, prom); // long move lacks promo specification!
4608           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4609                 if(appData.debugMode)
4610                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4611                 safeStrCpy(move_str, buf, MSG_SIZ);
4612           }
4613           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4614                                 &fromX, &fromY, &toX, &toY, &promoChar)
4615                || ParseOneMove(buf, moveNum - 1, &moveType,
4616                                 &fromX, &fromY, &toX, &toY, &promoChar);
4617           // end of long SAN patch
4618           if (valid) {
4619             (void) CoordsToAlgebraic(boards[moveNum - 1],
4620                                      PosFlags(moveNum - 1),
4621                                      fromY, fromX, toY, toX, promoChar,
4622                                      parseList[moveNum-1]);
4623             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4624               case MT_NONE:
4625               case MT_STALEMATE:
4626               default:
4627                 break;
4628               case MT_CHECK:
4629                 if(gameInfo.variant != VariantShogi)
4630                     strcat(parseList[moveNum - 1], "+");
4631                 break;
4632               case MT_CHECKMATE:
4633               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4634                 strcat(parseList[moveNum - 1], "#");
4635                 break;
4636             }
4637             strcat(parseList[moveNum - 1], " ");
4638             strcat(parseList[moveNum - 1], elapsed_time);
4639             /* currentMoveString is set as a side-effect of ParseOneMove */
4640             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4641             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4642             strcat(moveList[moveNum - 1], "\n");
4643
4644             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4645                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4646               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4647                 ChessSquare old, new = boards[moveNum][k][j];
4648                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4649                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4650                   if(old == new) continue;
4651                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4652                   else if(new == WhiteWazir || new == BlackWazir) {
4653                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4654                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4655                       else boards[moveNum][k][j] = old; // preserve type of Gold
4656                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4657                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4658               }
4659           } else {
4660             /* Move from ICS was illegal!?  Punt. */
4661             if (appData.debugMode) {
4662               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4663               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4664             }
4665             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4666             strcat(parseList[moveNum - 1], " ");
4667             strcat(parseList[moveNum - 1], elapsed_time);
4668             moveList[moveNum - 1][0] = NULLCHAR;
4669             fromX = fromY = toX = toY = -1;
4670           }
4671         }
4672   if (appData.debugMode) {
4673     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4674     setbuf(debugFP, NULL);
4675   }
4676
4677 #if ZIPPY
4678         /* Send move to chess program (BEFORE animating it). */
4679         if (appData.zippyPlay && !newGame && newMove &&
4680            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4681
4682             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4683                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4684                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4685                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4686                             move_str);
4687                     DisplayError(str, 0);
4688                 } else {
4689                     if (first.sendTime) {
4690                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4691                     }
4692                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4693                     if (firstMove && !bookHit) {
4694                         firstMove = FALSE;
4695                         if (first.useColors) {
4696                           SendToProgram(gameMode == IcsPlayingWhite ?
4697                                         "white\ngo\n" :
4698                                         "black\ngo\n", &first);
4699                         } else {
4700                           SendToProgram("go\n", &first);
4701                         }
4702                         first.maybeThinking = TRUE;
4703                     }
4704                 }
4705             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4706               if (moveList[moveNum - 1][0] == NULLCHAR) {
4707                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4708                 DisplayError(str, 0);
4709               } else {
4710                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4711                 SendMoveToProgram(moveNum - 1, &first);
4712               }
4713             }
4714         }
4715 #endif
4716     }
4717
4718     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4719         /* If move comes from a remote source, animate it.  If it
4720            isn't remote, it will have already been animated. */
4721         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4722             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4723         }
4724         if (!pausing && appData.highlightLastMove) {
4725             SetHighlights(fromX, fromY, toX, toY);
4726         }
4727     }
4728
4729     /* Start the clocks */
4730     whiteFlag = blackFlag = FALSE;
4731     appData.clockMode = !(basetime == 0 && increment == 0);
4732     if (ticking == 0) {
4733       ics_clock_paused = TRUE;
4734       StopClocks();
4735     } else if (ticking == 1) {
4736       ics_clock_paused = FALSE;
4737     }
4738     if (gameMode == IcsIdle ||
4739         relation == RELATION_OBSERVING_STATIC ||
4740         relation == RELATION_EXAMINING ||
4741         ics_clock_paused)
4742       DisplayBothClocks();
4743     else
4744       StartClocks();
4745
4746     /* Display opponents and material strengths */
4747     if (gameInfo.variant != VariantBughouse &&
4748         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4749         if (tinyLayout || smallLayout) {
4750             if(gameInfo.variant == VariantNormal)
4751               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4752                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4753                     basetime, increment);
4754             else
4755               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4756                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4757                     basetime, increment, (int) gameInfo.variant);
4758         } else {
4759             if(gameInfo.variant == VariantNormal)
4760               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4761                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4762                     basetime, increment);
4763             else
4764               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment, VariantName(gameInfo.variant));
4767         }
4768         DisplayTitle(str);
4769   if (appData.debugMode) {
4770     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4771   }
4772     }
4773
4774
4775     /* Display the board */
4776     if (!pausing && !appData.noGUI) {
4777
4778       if (appData.premove)
4779           if (!gotPremove ||
4780              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4781              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4782               ClearPremoveHighlights();
4783
4784       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4785         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4786       DrawPosition(j, boards[currentMove]);
4787
4788       DisplayMove(moveNum - 1);
4789       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4790             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4791               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4792         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4793       }
4794     }
4795
4796     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4797 #if ZIPPY
4798     if(bookHit) { // [HGM] book: simulate book reply
4799         static char bookMove[MSG_SIZ]; // a bit generous?
4800
4801         programStats.nodes = programStats.depth = programStats.time =
4802         programStats.score = programStats.got_only_move = 0;
4803         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4804
4805         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4806         strcat(bookMove, bookHit);
4807         HandleMachineMove(bookMove, &first);
4808     }
4809 #endif
4810 }
4811
4812 void
4813 GetMoveListEvent()
4814 {
4815     char buf[MSG_SIZ];
4816     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4817         ics_getting_history = H_REQUESTED;
4818         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4819         SendToICS(buf);
4820     }
4821 }
4822
4823 void
4824 AnalysisPeriodicEvent(force)
4825      int force;
4826 {
4827     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4828          && !force) || !appData.periodicUpdates)
4829       return;
4830
4831     /* Send . command to Crafty to collect stats */
4832     SendToProgram(".\n", &first);
4833
4834     /* Don't send another until we get a response (this makes
4835        us stop sending to old Crafty's which don't understand
4836        the "." command (sending illegal cmds resets node count & time,
4837        which looks bad)) */
4838     programStats.ok_to_send = 0;
4839 }
4840
4841 void ics_update_width(new_width)
4842         int new_width;
4843 {
4844         ics_printf("set width %d\n", new_width);
4845 }
4846
4847 void
4848 SendMoveToProgram(moveNum, cps)
4849      int moveNum;
4850      ChessProgramState *cps;
4851 {
4852     char buf[MSG_SIZ];
4853
4854     if (cps->useUsermove) {
4855       SendToProgram("usermove ", cps);
4856     }
4857     if (cps->useSAN) {
4858       char *space;
4859       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4860         int len = space - parseList[moveNum];
4861         memcpy(buf, parseList[moveNum], len);
4862         buf[len++] = '\n';
4863         buf[len] = NULLCHAR;
4864       } else {
4865         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4866       }
4867       SendToProgram(buf, cps);
4868     } else {
4869       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4870         AlphaRank(moveList[moveNum], 4);
4871         SendToProgram(moveList[moveNum], cps);
4872         AlphaRank(moveList[moveNum], 4); // and back
4873       } else
4874       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4875        * the engine. It would be nice to have a better way to identify castle
4876        * moves here. */
4877       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4878                                                                          && cps->useOOCastle) {
4879         int fromX = moveList[moveNum][0] - AAA;
4880         int fromY = moveList[moveNum][1] - ONE;
4881         int toX = moveList[moveNum][2] - AAA;
4882         int toY = moveList[moveNum][3] - ONE;
4883         if((boards[moveNum][fromY][fromX] == WhiteKing
4884             && boards[moveNum][toY][toX] == WhiteRook)
4885            || (boards[moveNum][fromY][fromX] == BlackKing
4886                && boards[moveNum][toY][toX] == BlackRook)) {
4887           if(toX > fromX) SendToProgram("O-O\n", cps);
4888           else SendToProgram("O-O-O\n", cps);
4889         }
4890         else SendToProgram(moveList[moveNum], cps);
4891       }
4892       else SendToProgram(moveList[moveNum], cps);
4893       /* End of additions by Tord */
4894     }
4895
4896     /* [HGM] setting up the opening has brought engine in force mode! */
4897     /*       Send 'go' if we are in a mode where machine should play. */
4898     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4899         (gameMode == TwoMachinesPlay   ||
4900 #if ZIPPY
4901          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4902 #endif
4903          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4904         SendToProgram("go\n", cps);
4905   if (appData.debugMode) {
4906     fprintf(debugFP, "(extra)\n");
4907   }
4908     }
4909     setboardSpoiledMachineBlack = 0;
4910 }
4911
4912 void
4913 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4914      ChessMove moveType;
4915      int fromX, fromY, toX, toY;
4916      char promoChar;
4917 {
4918     char user_move[MSG_SIZ];
4919
4920     switch (moveType) {
4921       default:
4922         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4923                 (int)moveType, fromX, fromY, toX, toY);
4924         DisplayError(user_move + strlen("say "), 0);
4925         break;
4926       case WhiteKingSideCastle:
4927       case BlackKingSideCastle:
4928       case WhiteQueenSideCastleWild:
4929       case BlackQueenSideCastleWild:
4930       /* PUSH Fabien */
4931       case WhiteHSideCastleFR:
4932       case BlackHSideCastleFR:
4933       /* POP Fabien */
4934         snprintf(user_move, MSG_SIZ, "o-o\n");
4935         break;
4936       case WhiteQueenSideCastle:
4937       case BlackQueenSideCastle:
4938       case WhiteKingSideCastleWild:
4939       case BlackKingSideCastleWild:
4940       /* PUSH Fabien */
4941       case WhiteASideCastleFR:
4942       case BlackASideCastleFR:
4943       /* POP Fabien */
4944         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4945         break;
4946       case WhiteNonPromotion:
4947       case BlackNonPromotion:
4948         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4949         break;
4950       case WhitePromotion:
4951       case BlackPromotion:
4952         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4953           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4954                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4955                 PieceToChar(WhiteFerz));
4956         else if(gameInfo.variant == VariantGreat)
4957           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4958                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4959                 PieceToChar(WhiteMan));
4960         else
4961           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4962                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4963                 promoChar);
4964         break;
4965       case WhiteDrop:
4966       case BlackDrop:
4967       drop:
4968         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4969                  ToUpper(PieceToChar((ChessSquare) fromX)),
4970                  AAA + toX, ONE + toY);
4971         break;
4972       case IllegalMove:  /* could be a variant we don't quite understand */
4973         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4974       case NormalMove:
4975       case WhiteCapturesEnPassant:
4976       case BlackCapturesEnPassant:
4977         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4978                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4979         break;
4980     }
4981     SendToICS(user_move);
4982     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4983         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4984 }
4985
4986 void
4987 UploadGameEvent()
4988 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4989     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4990     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4991     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4992         DisplayError("You cannot do this while you are playing or observing", 0);
4993         return;
4994     }
4995     if(gameMode != IcsExamining) { // is this ever not the case?
4996         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4997
4998         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4999           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5000         } else { // on FICS we must first go to general examine mode
5001           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5002         }
5003         if(gameInfo.variant != VariantNormal) {
5004             // try figure out wild number, as xboard names are not always valid on ICS
5005             for(i=1; i<=36; i++) {
5006               snprintf(buf, MSG_SIZ, "wild/%d", i);
5007                 if(StringToVariant(buf) == gameInfo.variant) break;
5008             }
5009             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5010             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5011             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5012         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5013         SendToICS(ics_prefix);
5014         SendToICS(buf);
5015         if(startedFromSetupPosition || backwardMostMove != 0) {
5016           fen = PositionToFEN(backwardMostMove, NULL);
5017           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5018             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5019             SendToICS(buf);
5020           } else { // FICS: everything has to set by separate bsetup commands
5021             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5022             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5023             SendToICS(buf);
5024             if(!WhiteOnMove(backwardMostMove)) {
5025                 SendToICS("bsetup tomove black\n");
5026             }
5027             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5028             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5029             SendToICS(buf);
5030             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5031             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5032             SendToICS(buf);
5033             i = boards[backwardMostMove][EP_STATUS];
5034             if(i >= 0) { // set e.p.
5035               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5036                 SendToICS(buf);
5037             }
5038             bsetup++;
5039           }
5040         }
5041       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5042             SendToICS("bsetup done\n"); // switch to normal examining.
5043     }
5044     for(i = backwardMostMove; i<last; i++) {
5045         char buf[20];
5046         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5047         SendToICS(buf);
5048     }
5049     SendToICS(ics_prefix);
5050     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5051 }
5052
5053 void
5054 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5055      int rf, ff, rt, ft;
5056      char promoChar;
5057      char move[7];
5058 {
5059     if (rf == DROP_RANK) {
5060       sprintf(move, "%c@%c%c\n",
5061                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5062     } else {
5063         if (promoChar == 'x' || promoChar == NULLCHAR) {
5064           sprintf(move, "%c%c%c%c\n",
5065                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5066         } else {
5067             sprintf(move, "%c%c%c%c%c\n",
5068                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5069         }
5070     }
5071 }
5072
5073 void
5074 ProcessICSInitScript(f)
5075      FILE *f;
5076 {
5077     char buf[MSG_SIZ];
5078
5079     while (fgets(buf, MSG_SIZ, f)) {
5080         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5081     }
5082
5083     fclose(f);
5084 }
5085
5086
5087 static int lastX, lastY, selectFlag, dragging;
5088
5089 void
5090 Sweep(int step)
5091 {
5092     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5093     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5094     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5095     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5096     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5097     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5098     do {
5099         promoSweep -= step;
5100         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5101         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5102         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5103         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5104         if(!step) step = 1;
5105     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5106             appData.testLegality && (promoSweep == king ||
5107             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5108     ChangeDragPiece(promoSweep);
5109 }
5110
5111 int PromoScroll(int x, int y)
5112 {
5113   int step = 0;
5114
5115   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5116   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5117   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5118   if(!step) return FALSE;
5119   lastX = x; lastY = y;
5120   if((promoSweep < BlackPawn) == flipView) step = -step;
5121   if(step > 0) selectFlag = 1;
5122   if(!selectFlag) Sweep(step);
5123   return FALSE;
5124 }
5125
5126 void
5127 NextPiece(int step)
5128 {
5129     ChessSquare piece = boards[currentMove][toY][toX];
5130     do {
5131         pieceSweep -= step;
5132         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5133         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5134         if(!step) step = -1;
5135     } while(PieceToChar(pieceSweep) == '.');
5136     boards[currentMove][toY][toX] = pieceSweep;
5137     DrawPosition(FALSE, boards[currentMove]);
5138     boards[currentMove][toY][toX] = piece;
5139 }
5140 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5141 void
5142 AlphaRank(char *move, int n)
5143 {
5144 //    char *p = move, c; int x, y;
5145
5146     if (appData.debugMode) {
5147         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5148     }
5149
5150     if(move[1]=='*' &&
5151        move[2]>='0' && move[2]<='9' &&
5152        move[3]>='a' && move[3]<='x'    ) {
5153         move[1] = '@';
5154         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5155         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5156     } else
5157     if(move[0]>='0' && move[0]<='9' &&
5158        move[1]>='a' && move[1]<='x' &&
5159        move[2]>='0' && move[2]<='9' &&
5160        move[3]>='a' && move[3]<='x'    ) {
5161         /* input move, Shogi -> normal */
5162         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5163         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5164         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5165         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5166     } else
5167     if(move[1]=='@' &&
5168        move[3]>='0' && move[3]<='9' &&
5169        move[2]>='a' && move[2]<='x'    ) {
5170         move[1] = '*';
5171         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5172         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5173     } else
5174     if(
5175        move[0]>='a' && move[0]<='x' &&
5176        move[3]>='0' && move[3]<='9' &&
5177        move[2]>='a' && move[2]<='x'    ) {
5178          /* output move, normal -> Shogi */
5179         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5180         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5181         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5182         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5183         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5184     }
5185     if (appData.debugMode) {
5186         fprintf(debugFP, "   out = '%s'\n", move);
5187     }
5188 }
5189
5190 char yy_textstr[8000];
5191
5192 /* Parser for moves from gnuchess, ICS, or user typein box */
5193 Boolean
5194 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5195      char *move;
5196      int moveNum;
5197      ChessMove *moveType;
5198      int *fromX, *fromY, *toX, *toY;
5199      char *promoChar;
5200 {
5201     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5202
5203     switch (*moveType) {
5204       case WhitePromotion:
5205       case BlackPromotion:
5206       case WhiteNonPromotion:
5207       case BlackNonPromotion:
5208       case NormalMove:
5209       case WhiteCapturesEnPassant:
5210       case BlackCapturesEnPassant:
5211       case WhiteKingSideCastle:
5212       case WhiteQueenSideCastle:
5213       case BlackKingSideCastle:
5214       case BlackQueenSideCastle:
5215       case WhiteKingSideCastleWild:
5216       case WhiteQueenSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       case BlackQueenSideCastleWild:
5219       /* Code added by Tord: */
5220       case WhiteHSideCastleFR:
5221       case WhiteASideCastleFR:
5222       case BlackHSideCastleFR:
5223       case BlackASideCastleFR:
5224       /* End of code added by Tord */
5225       case IllegalMove:         /* bug or odd chess variant */
5226         *fromX = currentMoveString[0] - AAA;
5227         *fromY = currentMoveString[1] - ONE;
5228         *toX = currentMoveString[2] - AAA;
5229         *toY = currentMoveString[3] - ONE;
5230         *promoChar = currentMoveString[4];
5231         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5232             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5233     if (appData.debugMode) {
5234         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5235     }
5236             *fromX = *fromY = *toX = *toY = 0;
5237             return FALSE;
5238         }
5239         if (appData.testLegality) {
5240           return (*moveType != IllegalMove);
5241         } else {
5242           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5243                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5244         }
5245
5246       case WhiteDrop:
5247       case BlackDrop:
5248         *fromX = *moveType == WhiteDrop ?
5249           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5250           (int) CharToPiece(ToLower(currentMoveString[0]));
5251         *fromY = DROP_RANK;
5252         *toX = currentMoveString[2] - AAA;
5253         *toY = currentMoveString[3] - ONE;
5254         *promoChar = NULLCHAR;
5255         return TRUE;
5256
5257       case AmbiguousMove:
5258       case ImpossibleMove:
5259       case EndOfFile:
5260       case ElapsedTime:
5261       case Comment:
5262       case PGNTag:
5263       case NAG:
5264       case WhiteWins:
5265       case BlackWins:
5266       case GameIsDrawn:
5267       default:
5268     if (appData.debugMode) {
5269         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5270     }
5271         /* bug? */
5272         *fromX = *fromY = *toX = *toY = 0;
5273         *promoChar = NULLCHAR;
5274         return FALSE;
5275     }
5276 }
5277
5278 Boolean pushed = FALSE;
5279 char *lastParseAttempt;
5280
5281 void
5282 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5283 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5284   int fromX, fromY, toX, toY; char promoChar;
5285   ChessMove moveType;
5286   Boolean valid;
5287   int nr = 0;
5288
5289   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5290     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5291     pushed = TRUE;
5292   }
5293   endPV = forwardMostMove;
5294   do {
5295     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5296     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5297     lastParseAttempt = pv;
5298     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5299 if(appData.debugMode){
5300 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);
5301 }
5302     if(!valid && nr == 0 &&
5303        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5304         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5305         // Hande case where played move is different from leading PV move
5306         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5307         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5308         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5309         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5310           endPV += 2; // if position different, keep this
5311           moveList[endPV-1][0] = fromX + AAA;
5312           moveList[endPV-1][1] = fromY + ONE;
5313           moveList[endPV-1][2] = toX + AAA;
5314           moveList[endPV-1][3] = toY + ONE;
5315           parseList[endPV-1][0] = NULLCHAR;
5316           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5317         }
5318       }
5319     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5320     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5321     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5322     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5323         valid++; // allow comments in PV
5324         continue;
5325     }
5326     nr++;
5327     if(endPV+1 > framePtr) break; // no space, truncate
5328     if(!valid) break;
5329     endPV++;
5330     CopyBoard(boards[endPV], boards[endPV-1]);
5331     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5332     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5333     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5334     CoordsToAlgebraic(boards[endPV - 1],
5335                              PosFlags(endPV - 1),
5336                              fromY, fromX, toY, toX, promoChar,
5337                              parseList[endPV - 1]);
5338   } while(valid);
5339   if(atEnd == 2) return; // used hidden, for PV conversion
5340   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5341   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5342   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5343                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5344   DrawPosition(TRUE, boards[currentMove]);
5345 }
5346
5347 int
5348 MultiPV(ChessProgramState *cps)
5349 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5350         int i;
5351         for(i=0; i<cps->nrOptions; i++)
5352             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5353                 return i;
5354         return -1;
5355 }
5356
5357 Boolean
5358 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5359 {
5360         int startPV, multi, lineStart, origIndex = index;
5361         char *p, buf2[MSG_SIZ];
5362
5363         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5364         lastX = x; lastY = y;
5365         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5366         lineStart = startPV = index;
5367         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5368         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5369         index = startPV;
5370         do{ while(buf[index] && buf[index] != '\n') index++;
5371         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5372         buf[index] = 0;
5373         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5374                 int n = first.option[multi].value;
5375                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5376                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5377                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5378                 first.option[multi].value = n;
5379                 *start = *end = 0;
5380                 return FALSE;
5381         }
5382         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5383         *start = startPV; *end = index-1;
5384         return TRUE;
5385 }
5386
5387 char *
5388 PvToSAN(char *pv)
5389 {
5390         static char buf[10*MSG_SIZ];
5391         int i, k=0, savedEnd=endPV;
5392         *buf = NULLCHAR;
5393         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5394         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5395         for(i = forwardMostMove; i<endPV; i++){
5396             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5397             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5398             k += strlen(buf+k);
5399         }
5400         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5401         if(forwardMostMove < savedEnd) PopInner(0);
5402         endPV = savedEnd;
5403         return buf;
5404 }
5405
5406 Boolean
5407 LoadPV(int x, int y)
5408 { // called on right mouse click to load PV
5409   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5410   lastX = x; lastY = y;
5411   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5412   return TRUE;
5413 }
5414
5415 void
5416 UnLoadPV()
5417 {
5418   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5419   if(endPV < 0) return;
5420   endPV = -1;
5421   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5422         Boolean saveAnimate = appData.animate;
5423         if(pushed) {
5424             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5425                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5426             } else storedGames--; // abandon shelved tail of original game
5427         }
5428         pushed = FALSE;
5429         forwardMostMove = currentMove;
5430         currentMove = oldFMM;
5431         appData.animate = FALSE;
5432         ToNrEvent(forwardMostMove);
5433         appData.animate = saveAnimate;
5434   }
5435   currentMove = forwardMostMove;
5436   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5437   ClearPremoveHighlights();
5438   DrawPosition(TRUE, boards[currentMove]);
5439 }
5440
5441 void
5442 MovePV(int x, int y, int h)
5443 { // step through PV based on mouse coordinates (called on mouse move)
5444   int margin = h>>3, step = 0;
5445
5446   // we must somehow check if right button is still down (might be released off board!)
5447   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5448   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5449   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5450   if(!step) return;
5451   lastX = x; lastY = y;
5452
5453   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5454   if(endPV < 0) return;
5455   if(y < margin) step = 1; else
5456   if(y > h - margin) step = -1;
5457   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5458   currentMove += step;
5459   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5460   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5461                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5462   DrawPosition(FALSE, boards[currentMove]);
5463 }
5464
5465
5466 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5467 // All positions will have equal probability, but the current method will not provide a unique
5468 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5469 #define DARK 1
5470 #define LITE 2
5471 #define ANY 3
5472
5473 int squaresLeft[4];
5474 int piecesLeft[(int)BlackPawn];
5475 int seed, nrOfShuffles;
5476
5477 void GetPositionNumber()
5478 {       // sets global variable seed
5479         int i;
5480
5481         seed = appData.defaultFrcPosition;
5482         if(seed < 0) { // randomize based on time for negative FRC position numbers
5483                 for(i=0; i<50; i++) seed += random();
5484                 seed = random() ^ random() >> 8 ^ random() << 8;
5485                 if(seed<0) seed = -seed;
5486         }
5487 }
5488
5489 int put(Board board, int pieceType, int rank, int n, int shade)
5490 // put the piece on the (n-1)-th empty squares of the given shade
5491 {
5492         int i;
5493
5494         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5495                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5496                         board[rank][i] = (ChessSquare) pieceType;
5497                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5498                         squaresLeft[ANY]--;
5499                         piecesLeft[pieceType]--;
5500                         return i;
5501                 }
5502         }
5503         return -1;
5504 }
5505
5506
5507 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5508 // calculate where the next piece goes, (any empty square), and put it there
5509 {
5510         int i;
5511
5512         i = seed % squaresLeft[shade];
5513         nrOfShuffles *= squaresLeft[shade];
5514         seed /= squaresLeft[shade];
5515         put(board, pieceType, rank, i, shade);
5516 }
5517
5518 void AddTwoPieces(Board board, int pieceType, int rank)
5519 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5520 {
5521         int i, n=squaresLeft[ANY], j=n-1, k;
5522
5523         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5524         i = seed % k;  // pick one
5525         nrOfShuffles *= k;
5526         seed /= k;
5527         while(i >= j) i -= j--;
5528         j = n - 1 - j; i += j;
5529         put(board, pieceType, rank, j, ANY);
5530         put(board, pieceType, rank, i, ANY);
5531 }
5532
5533 void SetUpShuffle(Board board, int number)
5534 {
5535         int i, p, first=1;
5536
5537         GetPositionNumber(); nrOfShuffles = 1;
5538
5539         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5540         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5541         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5542
5543         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5544
5545         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5546             p = (int) board[0][i];
5547             if(p < (int) BlackPawn) piecesLeft[p] ++;
5548             board[0][i] = EmptySquare;
5549         }
5550
5551         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5552             // shuffles restricted to allow normal castling put KRR first
5553             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5554                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5555             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5556                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5557             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5558                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5559             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5560                 put(board, WhiteRook, 0, 0, ANY);
5561             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5562         }
5563
5564         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5565             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5566             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5567                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5568                 while(piecesLeft[p] >= 2) {
5569                     AddOnePiece(board, p, 0, LITE);
5570                     AddOnePiece(board, p, 0, DARK);
5571                 }
5572                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5573             }
5574
5575         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5576             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5577             // but we leave King and Rooks for last, to possibly obey FRC restriction
5578             if(p == (int)WhiteRook) continue;
5579             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5580             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5581         }
5582
5583         // now everything is placed, except perhaps King (Unicorn) and Rooks
5584
5585         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5586             // Last King gets castling rights
5587             while(piecesLeft[(int)WhiteUnicorn]) {
5588                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5589                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5590             }
5591
5592             while(piecesLeft[(int)WhiteKing]) {
5593                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5594                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5595             }
5596
5597
5598         } else {
5599             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5600             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5601         }
5602
5603         // Only Rooks can be left; simply place them all
5604         while(piecesLeft[(int)WhiteRook]) {
5605                 i = put(board, WhiteRook, 0, 0, ANY);
5606                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5607                         if(first) {
5608                                 first=0;
5609                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5610                         }
5611                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5612                 }
5613         }
5614         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5615             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5616         }
5617
5618         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5619 }
5620
5621 int SetCharTable( char *table, const char * map )
5622 /* [HGM] moved here from winboard.c because of its general usefulness */
5623 /*       Basically a safe strcpy that uses the last character as King */
5624 {
5625     int result = FALSE; int NrPieces;
5626
5627     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5628                     && NrPieces >= 12 && !(NrPieces&1)) {
5629         int i; /* [HGM] Accept even length from 12 to 34 */
5630
5631         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5632         for( i=0; i<NrPieces/2-1; i++ ) {
5633             table[i] = map[i];
5634             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5635         }
5636         table[(int) WhiteKing]  = map[NrPieces/2-1];
5637         table[(int) BlackKing]  = map[NrPieces-1];
5638
5639         result = TRUE;
5640     }
5641
5642     return result;
5643 }
5644
5645 void Prelude(Board board)
5646 {       // [HGM] superchess: random selection of exo-pieces
5647         int i, j, k; ChessSquare p;
5648         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5649
5650         GetPositionNumber(); // use FRC position number
5651
5652         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5653             SetCharTable(pieceToChar, appData.pieceToCharTable);
5654             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5655                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5656         }
5657
5658         j = seed%4;                 seed /= 4;
5659         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5660         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5661         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5662         j = seed%3 + (seed%3 >= j); seed /= 3;
5663         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5664         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5665         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5666         j = seed%3;                 seed /= 3;
5667         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5668         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5669         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5670         j = seed%2 + (seed%2 >= j); seed /= 2;
5671         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5672         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5673         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5674         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5675         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5676         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5677         put(board, exoPieces[0],    0, 0, ANY);
5678         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5679 }
5680
5681 void
5682 InitPosition(redraw)
5683      int redraw;
5684 {
5685     ChessSquare (* pieces)[BOARD_FILES];
5686     int i, j, pawnRow, overrule,
5687     oldx = gameInfo.boardWidth,
5688     oldy = gameInfo.boardHeight,
5689     oldh = gameInfo.holdingsWidth;
5690     static int oldv;
5691
5692     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5693
5694     /* [AS] Initialize pv info list [HGM] and game status */
5695     {
5696         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5697             pvInfoList[i].depth = 0;
5698             boards[i][EP_STATUS] = EP_NONE;
5699             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5700         }
5701
5702         initialRulePlies = 0; /* 50-move counter start */
5703
5704         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5705         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5706     }
5707
5708
5709     /* [HGM] logic here is completely changed. In stead of full positions */
5710     /* the initialized data only consist of the two backranks. The switch */
5711     /* selects which one we will use, which is than copied to the Board   */
5712     /* initialPosition, which for the rest is initialized by Pawns and    */
5713     /* empty squares. This initial position is then copied to boards[0],  */
5714     /* possibly after shuffling, so that it remains available.            */
5715
5716     gameInfo.holdingsWidth = 0; /* default board sizes */
5717     gameInfo.boardWidth    = 8;
5718     gameInfo.boardHeight   = 8;
5719     gameInfo.holdingsSize  = 0;
5720     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5721     for(i=0; i<BOARD_FILES-2; i++)
5722       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5723     initialPosition[EP_STATUS] = EP_NONE;
5724     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5725     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5726          SetCharTable(pieceNickName, appData.pieceNickNames);
5727     else SetCharTable(pieceNickName, "............");
5728     pieces = FIDEArray;
5729
5730     switch (gameInfo.variant) {
5731     case VariantFischeRandom:
5732       shuffleOpenings = TRUE;
5733     default:
5734       break;
5735     case VariantShatranj:
5736       pieces = ShatranjArray;
5737       nrCastlingRights = 0;
5738       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5739       break;
5740     case VariantMakruk:
5741       pieces = makrukArray;
5742       nrCastlingRights = 0;
5743       startedFromSetupPosition = TRUE;
5744       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5745       break;
5746     case VariantTwoKings:
5747       pieces = twoKingsArray;
5748       break;
5749     case VariantCapaRandom:
5750       shuffleOpenings = TRUE;
5751     case VariantCapablanca:
5752       pieces = CapablancaArray;
5753       gameInfo.boardWidth = 10;
5754       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5755       break;
5756     case VariantGothic:
5757       pieces = GothicArray;
5758       gameInfo.boardWidth = 10;
5759       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5760       break;
5761     case VariantSChess:
5762       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5763       gameInfo.holdingsSize = 7;
5764       break;
5765     case VariantJanus:
5766       pieces = JanusArray;
5767       gameInfo.boardWidth = 10;
5768       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5769       nrCastlingRights = 6;
5770         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5771         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5772         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5773         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5774         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5775         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5776       break;
5777     case VariantFalcon:
5778       pieces = FalconArray;
5779       gameInfo.boardWidth = 10;
5780       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5781       break;
5782     case VariantXiangqi:
5783       pieces = XiangqiArray;
5784       gameInfo.boardWidth  = 9;
5785       gameInfo.boardHeight = 10;
5786       nrCastlingRights = 0;
5787       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5788       break;
5789     case VariantShogi:
5790       pieces = ShogiArray;
5791       gameInfo.boardWidth  = 9;
5792       gameInfo.boardHeight = 9;
5793       gameInfo.holdingsSize = 7;
5794       nrCastlingRights = 0;
5795       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5796       break;
5797     case VariantCourier:
5798       pieces = CourierArray;
5799       gameInfo.boardWidth  = 12;
5800       nrCastlingRights = 0;
5801       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5802       break;
5803     case VariantKnightmate:
5804       pieces = KnightmateArray;
5805       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5806       break;
5807     case VariantSpartan:
5808       pieces = SpartanArray;
5809       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5810       break;
5811     case VariantFairy:
5812       pieces = fairyArray;
5813       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5814       break;
5815     case VariantGreat:
5816       pieces = GreatArray;
5817       gameInfo.boardWidth = 10;
5818       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5819       gameInfo.holdingsSize = 8;
5820       break;
5821     case VariantSuper:
5822       pieces = FIDEArray;
5823       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5824       gameInfo.holdingsSize = 8;
5825       startedFromSetupPosition = TRUE;
5826       break;
5827     case VariantCrazyhouse:
5828     case VariantBughouse:
5829       pieces = FIDEArray;
5830       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5831       gameInfo.holdingsSize = 5;
5832       break;
5833     case VariantWildCastle:
5834       pieces = FIDEArray;
5835       /* !!?shuffle with kings guaranteed to be on d or e file */
5836       shuffleOpenings = 1;
5837       break;
5838     case VariantNoCastle:
5839       pieces = FIDEArray;
5840       nrCastlingRights = 0;
5841       /* !!?unconstrained back-rank shuffle */
5842       shuffleOpenings = 1;
5843       break;
5844     }
5845
5846     overrule = 0;
5847     if(appData.NrFiles >= 0) {
5848         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5849         gameInfo.boardWidth = appData.NrFiles;
5850     }
5851     if(appData.NrRanks >= 0) {
5852         gameInfo.boardHeight = appData.NrRanks;
5853     }
5854     if(appData.holdingsSize >= 0) {
5855         i = appData.holdingsSize;
5856         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5857         gameInfo.holdingsSize = i;
5858     }
5859     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5860     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5861         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5862
5863     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5864     if(pawnRow < 1) pawnRow = 1;
5865     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5866
5867     /* User pieceToChar list overrules defaults */
5868     if(appData.pieceToCharTable != NULL)
5869         SetCharTable(pieceToChar, appData.pieceToCharTable);
5870
5871     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5872
5873         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5874             s = (ChessSquare) 0; /* account holding counts in guard band */
5875         for( i=0; i<BOARD_HEIGHT; i++ )
5876             initialPosition[i][j] = s;
5877
5878         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5879         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5880         initialPosition[pawnRow][j] = WhitePawn;
5881         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5882         if(gameInfo.variant == VariantXiangqi) {
5883             if(j&1) {
5884                 initialPosition[pawnRow][j] =
5885                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5886                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5887                    initialPosition[2][j] = WhiteCannon;
5888                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5889                 }
5890             }
5891         }
5892         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5893     }
5894     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5895
5896             j=BOARD_LEFT+1;
5897             initialPosition[1][j] = WhiteBishop;
5898             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5899             j=BOARD_RGHT-2;
5900             initialPosition[1][j] = WhiteRook;
5901             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5902     }
5903
5904     if( nrCastlingRights == -1) {
5905         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5906         /*       This sets default castling rights from none to normal corners   */
5907         /* Variants with other castling rights must set them themselves above    */
5908         nrCastlingRights = 6;
5909
5910         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5911         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5912         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5913         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5914         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5915         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5916      }
5917
5918      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5919      if(gameInfo.variant == VariantGreat) { // promotion commoners
5920         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5921         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5922         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5923         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5924      }
5925      if( gameInfo.variant == VariantSChess ) {
5926       initialPosition[1][0] = BlackMarshall;
5927       initialPosition[2][0] = BlackAngel;
5928       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5929       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5930       initialPosition[1][1] = initialPosition[2][1] = 
5931       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5932      }
5933   if (appData.debugMode) {
5934     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5935   }
5936     if(shuffleOpenings) {
5937         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5938         startedFromSetupPosition = TRUE;
5939     }
5940     if(startedFromPositionFile) {
5941       /* [HGM] loadPos: use PositionFile for every new game */
5942       CopyBoard(initialPosition, filePosition);
5943       for(i=0; i<nrCastlingRights; i++)
5944           initialRights[i] = filePosition[CASTLING][i];
5945       startedFromSetupPosition = TRUE;
5946     }
5947
5948     CopyBoard(boards[0], initialPosition);
5949
5950     if(oldx != gameInfo.boardWidth ||
5951        oldy != gameInfo.boardHeight ||
5952        oldv != gameInfo.variant ||
5953        oldh != gameInfo.holdingsWidth
5954                                          )
5955             InitDrawingSizes(-2 ,0);
5956
5957     oldv = gameInfo.variant;
5958     if (redraw)
5959       DrawPosition(TRUE, boards[currentMove]);
5960 }
5961
5962 void
5963 SendBoard(cps, moveNum)
5964      ChessProgramState *cps;
5965      int moveNum;
5966 {
5967     char message[MSG_SIZ];
5968
5969     if (cps->useSetboard) {
5970       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5971       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5972       SendToProgram(message, cps);
5973       free(fen);
5974
5975     } else {
5976       ChessSquare *bp;
5977       int i, j;
5978       /* Kludge to set black to move, avoiding the troublesome and now
5979        * deprecated "black" command.
5980        */
5981       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5982         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5983
5984       SendToProgram("edit\n", cps);
5985       SendToProgram("#\n", cps);
5986       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5987         bp = &boards[moveNum][i][BOARD_LEFT];
5988         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5989           if ((int) *bp < (int) BlackPawn) {
5990             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5991                     AAA + j, ONE + i);
5992             if(message[0] == '+' || message[0] == '~') {
5993               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5994                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5995                         AAA + j, ONE + i);
5996             }
5997             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5998                 message[1] = BOARD_RGHT   - 1 - j + '1';
5999                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6000             }
6001             SendToProgram(message, cps);
6002           }
6003         }
6004       }
6005
6006       SendToProgram("c\n", cps);
6007       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6008         bp = &boards[moveNum][i][BOARD_LEFT];
6009         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6010           if (((int) *bp != (int) EmptySquare)
6011               && ((int) *bp >= (int) BlackPawn)) {
6012             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6013                     AAA + j, ONE + i);
6014             if(message[0] == '+' || message[0] == '~') {
6015               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6016                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6017                         AAA + j, ONE + i);
6018             }
6019             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6020                 message[1] = BOARD_RGHT   - 1 - j + '1';
6021                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6022             }
6023             SendToProgram(message, cps);
6024           }
6025         }
6026       }
6027
6028       SendToProgram(".\n", cps);
6029     }
6030     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6031 }
6032
6033 ChessSquare
6034 DefaultPromoChoice(int white)
6035 {
6036     ChessSquare result;
6037     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6038         result = WhiteFerz; // no choice
6039     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6040         result= WhiteKing; // in Suicide Q is the last thing we want
6041     else if(gameInfo.variant == VariantSpartan)
6042         result = white ? WhiteQueen : WhiteAngel;
6043     else result = WhiteQueen;
6044     if(!white) result = WHITE_TO_BLACK result;
6045     return result;
6046 }
6047
6048 static int autoQueen; // [HGM] oneclick
6049
6050 int
6051 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6052 {
6053     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6054     /* [HGM] add Shogi promotions */
6055     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6056     ChessSquare piece;
6057     ChessMove moveType;
6058     Boolean premove;
6059
6060     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6061     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6062
6063     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6064       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6065         return FALSE;
6066
6067     piece = boards[currentMove][fromY][fromX];
6068     if(gameInfo.variant == VariantShogi) {
6069         promotionZoneSize = BOARD_HEIGHT/3;
6070         highestPromotingPiece = (int)WhiteFerz;
6071     } else if(gameInfo.variant == VariantMakruk) {
6072         promotionZoneSize = 3;
6073     }
6074
6075     // Treat Lance as Pawn when it is not representing Amazon
6076     if(gameInfo.variant != VariantSuper) {
6077         if(piece == WhiteLance) piece = WhitePawn; else
6078         if(piece == BlackLance) piece = BlackPawn;
6079     }
6080
6081     // next weed out all moves that do not touch the promotion zone at all
6082     if((int)piece >= BlackPawn) {
6083         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6084              return FALSE;
6085         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6086     } else {
6087         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6088            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6089     }
6090
6091     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6092
6093     // weed out mandatory Shogi promotions
6094     if(gameInfo.variant == VariantShogi) {
6095         if(piece >= BlackPawn) {
6096             if(toY == 0 && piece == BlackPawn ||
6097                toY == 0 && piece == BlackQueen ||
6098                toY <= 1 && piece == BlackKnight) {
6099                 *promoChoice = '+';
6100                 return FALSE;
6101             }
6102         } else {
6103             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6104                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6105                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6106                 *promoChoice = '+';
6107                 return FALSE;
6108             }
6109         }
6110     }
6111
6112     // weed out obviously illegal Pawn moves
6113     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6114         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6115         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6116         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6117         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6118         // note we are not allowed to test for valid (non-)capture, due to premove
6119     }
6120
6121     // we either have a choice what to promote to, or (in Shogi) whether to promote
6122     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6123         *promoChoice = PieceToChar(BlackFerz);  // no choice
6124         return FALSE;
6125     }
6126     // no sense asking what we must promote to if it is going to explode...
6127     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6128         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6129         return FALSE;
6130     }
6131     // give caller the default choice even if we will not make it
6132     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6133     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6134     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6135                            && gameInfo.variant != VariantShogi
6136                            && gameInfo.variant != VariantSuper) return FALSE;
6137     if(autoQueen) return FALSE; // predetermined
6138
6139     // suppress promotion popup on illegal moves that are not premoves
6140     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6141               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6142     if(appData.testLegality && !premove) {
6143         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6144                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6145         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6146             return FALSE;
6147     }
6148
6149     return TRUE;
6150 }
6151
6152 int
6153 InPalace(row, column)
6154      int row, column;
6155 {   /* [HGM] for Xiangqi */
6156     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6157          column < (BOARD_WIDTH + 4)/2 &&
6158          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6159     return FALSE;
6160 }
6161
6162 int
6163 PieceForSquare (x, y)
6164      int x;
6165      int y;
6166 {
6167   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6168      return -1;
6169   else
6170      return boards[currentMove][y][x];
6171 }
6172
6173 int
6174 OKToStartUserMove(x, y)
6175      int x, y;
6176 {
6177     ChessSquare from_piece;
6178     int white_piece;
6179
6180     if (matchMode) return FALSE;
6181     if (gameMode == EditPosition) return TRUE;
6182
6183     if (x >= 0 && y >= 0)
6184       from_piece = boards[currentMove][y][x];
6185     else
6186       from_piece = EmptySquare;
6187
6188     if (from_piece == EmptySquare) return FALSE;
6189
6190     white_piece = (int)from_piece >= (int)WhitePawn &&
6191       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6192
6193     switch (gameMode) {
6194       case PlayFromGameFile:
6195       case AnalyzeFile:
6196       case TwoMachinesPlay:
6197       case EndOfGame:
6198         return FALSE;
6199
6200       case IcsObserving:
6201       case IcsIdle:
6202         return FALSE;
6203
6204       case MachinePlaysWhite:
6205       case IcsPlayingBlack:
6206         if (appData.zippyPlay) return FALSE;
6207         if (white_piece) {
6208             DisplayMoveError(_("You are playing Black"));
6209             return FALSE;
6210         }
6211         break;
6212
6213       case MachinePlaysBlack:
6214       case IcsPlayingWhite:
6215         if (appData.zippyPlay) return FALSE;
6216         if (!white_piece) {
6217             DisplayMoveError(_("You are playing White"));
6218             return FALSE;
6219         }
6220         break;
6221
6222       case EditGame:
6223         if (!white_piece && WhiteOnMove(currentMove)) {
6224             DisplayMoveError(_("It is White's turn"));
6225             return FALSE;
6226         }
6227         if (white_piece && !WhiteOnMove(currentMove)) {
6228             DisplayMoveError(_("It is Black's turn"));
6229             return FALSE;
6230         }
6231         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6232             /* Editing correspondence game history */
6233             /* Could disallow this or prompt for confirmation */
6234             cmailOldMove = -1;
6235         }
6236         break;
6237
6238       case BeginningOfGame:
6239         if (appData.icsActive) return FALSE;
6240         if (!appData.noChessProgram) {
6241             if (!white_piece) {
6242                 DisplayMoveError(_("You are playing White"));
6243                 return FALSE;
6244             }
6245         }
6246         break;
6247
6248       case Training:
6249         if (!white_piece && WhiteOnMove(currentMove)) {
6250             DisplayMoveError(_("It is White's turn"));
6251             return FALSE;
6252         }
6253         if (white_piece && !WhiteOnMove(currentMove)) {
6254             DisplayMoveError(_("It is Black's turn"));
6255             return FALSE;
6256         }
6257         break;
6258
6259       default:
6260       case IcsExamining:
6261         break;
6262     }
6263     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6264         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6265         && gameMode != AnalyzeFile && gameMode != Training) {
6266         DisplayMoveError(_("Displayed position is not current"));
6267         return FALSE;
6268     }
6269     return TRUE;
6270 }
6271
6272 Boolean
6273 OnlyMove(int *x, int *y, Boolean captures) {
6274     DisambiguateClosure cl;
6275     if (appData.zippyPlay) return FALSE;
6276     switch(gameMode) {
6277       case MachinePlaysBlack:
6278       case IcsPlayingWhite:
6279       case BeginningOfGame:
6280         if(!WhiteOnMove(currentMove)) return FALSE;
6281         break;
6282       case MachinePlaysWhite:
6283       case IcsPlayingBlack:
6284         if(WhiteOnMove(currentMove)) return FALSE;
6285         break;
6286       case EditGame:
6287         break;
6288       default:
6289         return FALSE;
6290     }
6291     cl.pieceIn = EmptySquare;
6292     cl.rfIn = *y;
6293     cl.ffIn = *x;
6294     cl.rtIn = -1;
6295     cl.ftIn = -1;
6296     cl.promoCharIn = NULLCHAR;
6297     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6298     if( cl.kind == NormalMove ||
6299         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6300         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6301         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6302       fromX = cl.ff;
6303       fromY = cl.rf;
6304       *x = cl.ft;
6305       *y = cl.rt;
6306       return TRUE;
6307     }
6308     if(cl.kind != ImpossibleMove) return FALSE;
6309     cl.pieceIn = EmptySquare;
6310     cl.rfIn = -1;
6311     cl.ffIn = -1;
6312     cl.rtIn = *y;
6313     cl.ftIn = *x;
6314     cl.promoCharIn = NULLCHAR;
6315     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6316     if( cl.kind == NormalMove ||
6317         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6318         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6319         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6320       fromX = cl.ff;
6321       fromY = cl.rf;
6322       *x = cl.ft;
6323       *y = cl.rt;
6324       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6325       return TRUE;
6326     }
6327     return FALSE;
6328 }
6329
6330 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6331 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6332 int lastLoadGameUseList = FALSE;
6333 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6334 ChessMove lastLoadGameStart = EndOfFile;
6335
6336 void
6337 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6338      int fromX, fromY, toX, toY;
6339      int promoChar;
6340 {
6341     ChessMove moveType;
6342     ChessSquare pdown, pup;
6343
6344     /* Check if the user is playing in turn.  This is complicated because we
6345        let the user "pick up" a piece before it is his turn.  So the piece he
6346        tried to pick up may have been captured by the time he puts it down!
6347        Therefore we use the color the user is supposed to be playing in this
6348        test, not the color of the piece that is currently on the starting
6349        square---except in EditGame mode, where the user is playing both
6350        sides; fortunately there the capture race can't happen.  (It can
6351        now happen in IcsExamining mode, but that's just too bad.  The user
6352        will get a somewhat confusing message in that case.)
6353        */
6354
6355     switch (gameMode) {
6356       case PlayFromGameFile:
6357       case AnalyzeFile:
6358       case TwoMachinesPlay:
6359       case EndOfGame:
6360       case IcsObserving:
6361       case IcsIdle:
6362         /* We switched into a game mode where moves are not accepted,
6363            perhaps while the mouse button was down. */
6364         return;
6365
6366       case MachinePlaysWhite:
6367         /* User is moving for Black */
6368         if (WhiteOnMove(currentMove)) {
6369             DisplayMoveError(_("It is White's turn"));
6370             return;
6371         }
6372         break;
6373
6374       case MachinePlaysBlack:
6375         /* User is moving for White */
6376         if (!WhiteOnMove(currentMove)) {
6377             DisplayMoveError(_("It is Black's turn"));
6378             return;
6379         }
6380         break;
6381
6382       case EditGame:
6383       case IcsExamining:
6384       case BeginningOfGame:
6385       case AnalyzeMode:
6386       case Training:
6387         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6388         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6389             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6390             /* User is moving for Black */
6391             if (WhiteOnMove(currentMove)) {
6392                 DisplayMoveError(_("It is White's turn"));
6393                 return;
6394             }
6395         } else {
6396             /* User is moving for White */
6397             if (!WhiteOnMove(currentMove)) {
6398                 DisplayMoveError(_("It is Black's turn"));
6399                 return;
6400             }
6401         }
6402         break;
6403
6404       case IcsPlayingBlack:
6405         /* User is moving for Black */
6406         if (WhiteOnMove(currentMove)) {
6407             if (!appData.premove) {
6408                 DisplayMoveError(_("It is White's turn"));
6409             } else if (toX >= 0 && toY >= 0) {
6410                 premoveToX = toX;
6411                 premoveToY = toY;
6412                 premoveFromX = fromX;
6413                 premoveFromY = fromY;
6414                 premovePromoChar = promoChar;
6415                 gotPremove = 1;
6416                 if (appData.debugMode)
6417                     fprintf(debugFP, "Got premove: fromX %d,"
6418                             "fromY %d, toX %d, toY %d\n",
6419                             fromX, fromY, toX, toY);
6420             }
6421             return;
6422         }
6423         break;
6424
6425       case IcsPlayingWhite:
6426         /* User is moving for White */
6427         if (!WhiteOnMove(currentMove)) {
6428             if (!appData.premove) {
6429                 DisplayMoveError(_("It is Black's turn"));
6430             } else if (toX >= 0 && toY >= 0) {
6431                 premoveToX = toX;
6432                 premoveToY = toY;
6433                 premoveFromX = fromX;
6434                 premoveFromY = fromY;
6435                 premovePromoChar = promoChar;
6436                 gotPremove = 1;
6437                 if (appData.debugMode)
6438                     fprintf(debugFP, "Got premove: fromX %d,"
6439                             "fromY %d, toX %d, toY %d\n",
6440                             fromX, fromY, toX, toY);
6441             }
6442             return;
6443         }
6444         break;
6445
6446       default:
6447         break;
6448
6449       case EditPosition:
6450         /* EditPosition, empty square, or different color piece;
6451            click-click move is possible */
6452         if (toX == -2 || toY == -2) {
6453             boards[0][fromY][fromX] = EmptySquare;
6454             DrawPosition(FALSE, boards[currentMove]);
6455             return;
6456         } else if (toX >= 0 && toY >= 0) {
6457             boards[0][toY][toX] = boards[0][fromY][fromX];
6458             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6459                 if(boards[0][fromY][0] != EmptySquare) {
6460                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6461                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6462                 }
6463             } else
6464             if(fromX == BOARD_RGHT+1) {
6465                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6466                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6467                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6468                 }
6469             } else
6470             boards[0][fromY][fromX] = EmptySquare;
6471             DrawPosition(FALSE, boards[currentMove]);
6472             return;
6473         }
6474         return;
6475     }
6476
6477     if(toX < 0 || toY < 0) return;
6478     pdown = boards[currentMove][fromY][fromX];
6479     pup = boards[currentMove][toY][toX];
6480
6481     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6482     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6483          if( pup != EmptySquare ) return;
6484          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6485            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6486                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6487            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6488            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6489            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6490            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6491          fromY = DROP_RANK;
6492     }
6493
6494     /* [HGM] always test for legality, to get promotion info */
6495     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6496                                          fromY, fromX, toY, toX, promoChar);
6497     /* [HGM] but possibly ignore an IllegalMove result */
6498     if (appData.testLegality) {
6499         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6500             DisplayMoveError(_("Illegal move"));
6501             return;
6502         }
6503     }
6504
6505     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6506 }
6507
6508 /* Common tail of UserMoveEvent and DropMenuEvent */
6509 int
6510 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6511      ChessMove moveType;
6512      int fromX, fromY, toX, toY;
6513      /*char*/int promoChar;
6514 {
6515     char *bookHit = 0;
6516
6517     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6518         // [HGM] superchess: suppress promotions to non-available piece
6519         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6520         if(WhiteOnMove(currentMove)) {
6521             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6522         } else {
6523             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6524         }
6525     }
6526
6527     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6528        move type in caller when we know the move is a legal promotion */
6529     if(moveType == NormalMove && promoChar)
6530         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6531
6532     /* [HGM] <popupFix> The following if has been moved here from
6533        UserMoveEvent(). Because it seemed to belong here (why not allow
6534        piece drops in training games?), and because it can only be
6535        performed after it is known to what we promote. */
6536     if (gameMode == Training) {
6537       /* compare the move played on the board to the next move in the
6538        * game. If they match, display the move and the opponent's response.
6539        * If they don't match, display an error message.
6540        */
6541       int saveAnimate;
6542       Board testBoard;
6543       CopyBoard(testBoard, boards[currentMove]);
6544       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6545
6546       if (CompareBoards(testBoard, boards[currentMove+1])) {
6547         ForwardInner(currentMove+1);
6548
6549         /* Autoplay the opponent's response.
6550          * if appData.animate was TRUE when Training mode was entered,
6551          * the response will be animated.
6552          */
6553         saveAnimate = appData.animate;
6554         appData.animate = animateTraining;
6555         ForwardInner(currentMove+1);
6556         appData.animate = saveAnimate;
6557
6558         /* check for the end of the game */
6559         if (currentMove >= forwardMostMove) {
6560           gameMode = PlayFromGameFile;
6561           ModeHighlight();
6562           SetTrainingModeOff();
6563           DisplayInformation(_("End of game"));
6564         }
6565       } else {
6566         DisplayError(_("Incorrect move"), 0);
6567       }
6568       return 1;
6569     }
6570
6571   /* Ok, now we know that the move is good, so we can kill
6572      the previous line in Analysis Mode */
6573   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6574                                 && currentMove < forwardMostMove) {
6575     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6576     else forwardMostMove = currentMove;
6577   }
6578
6579   /* If we need the chess program but it's dead, restart it */
6580   ResurrectChessProgram();
6581
6582   /* A user move restarts a paused game*/
6583   if (pausing)
6584     PauseEvent();
6585
6586   thinkOutput[0] = NULLCHAR;
6587
6588   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6589
6590   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6591     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6592     return 1;
6593   }
6594
6595   if (gameMode == BeginningOfGame) {
6596     if (appData.noChessProgram) {
6597       gameMode = EditGame;
6598       SetGameInfo();
6599     } else {
6600       char buf[MSG_SIZ];
6601       gameMode = MachinePlaysBlack;
6602       StartClocks();
6603       SetGameInfo();
6604       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6605       DisplayTitle(buf);
6606       if (first.sendName) {
6607         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6608         SendToProgram(buf, &first);
6609       }
6610       StartClocks();
6611     }
6612     ModeHighlight();
6613   }
6614
6615   /* Relay move to ICS or chess engine */
6616   if (appData.icsActive) {
6617     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6618         gameMode == IcsExamining) {
6619       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6620         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6621         SendToICS("draw ");
6622         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6623       }
6624       // also send plain move, in case ICS does not understand atomic claims
6625       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6626       ics_user_moved = 1;
6627     }
6628   } else {
6629     if (first.sendTime && (gameMode == BeginningOfGame ||
6630                            gameMode == MachinePlaysWhite ||
6631                            gameMode == MachinePlaysBlack)) {
6632       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6633     }
6634     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6635          // [HGM] book: if program might be playing, let it use book
6636         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6637         first.maybeThinking = TRUE;
6638     } else SendMoveToProgram(forwardMostMove-1, &first);
6639     if (currentMove == cmailOldMove + 1) {
6640       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6641     }
6642   }
6643
6644   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645
6646   switch (gameMode) {
6647   case EditGame:
6648     if(appData.testLegality)
6649     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6650     case MT_NONE:
6651     case MT_CHECK:
6652       break;
6653     case MT_CHECKMATE:
6654     case MT_STAINMATE:
6655       if (WhiteOnMove(currentMove)) {
6656         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6657       } else {
6658         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6659       }
6660       break;
6661     case MT_STALEMATE:
6662       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6663       break;
6664     }
6665     break;
6666
6667   case MachinePlaysBlack:
6668   case MachinePlaysWhite:
6669     /* disable certain menu options while machine is thinking */
6670     SetMachineThinkingEnables();
6671     break;
6672
6673   default:
6674     break;
6675   }
6676
6677   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6678   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6679
6680   if(bookHit) { // [HGM] book: simulate book reply
6681         static char bookMove[MSG_SIZ]; // a bit generous?
6682
6683         programStats.nodes = programStats.depth = programStats.time =
6684         programStats.score = programStats.got_only_move = 0;
6685         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6686
6687         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6688         strcat(bookMove, bookHit);
6689         HandleMachineMove(bookMove, &first);
6690   }
6691   return 1;
6692 }
6693
6694 void
6695 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6696      Board board;
6697      int flags;
6698      ChessMove kind;
6699      int rf, ff, rt, ft;
6700      VOIDSTAR closure;
6701 {
6702     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6703     Markers *m = (Markers *) closure;
6704     if(rf == fromY && ff == fromX)
6705         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6706                          || kind == WhiteCapturesEnPassant
6707                          || kind == BlackCapturesEnPassant);
6708     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6709 }
6710
6711 void
6712 MarkTargetSquares(int clear)
6713 {
6714   int x, y;
6715   if(!appData.markers || !appData.highlightDragging ||
6716      !appData.testLegality || gameMode == EditPosition) return;
6717   if(clear) {
6718     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6719   } else {
6720     int capt = 0;
6721     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6722     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6723       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6724       if(capt)
6725       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6726     }
6727   }
6728   DrawPosition(TRUE, NULL);
6729 }
6730
6731 int
6732 Explode(Board board, int fromX, int fromY, int toX, int toY)
6733 {
6734     if(gameInfo.variant == VariantAtomic &&
6735        (board[toY][toX] != EmptySquare ||                     // capture?
6736         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6737                          board[fromY][fromX] == BlackPawn   )
6738       )) {
6739         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6740         return TRUE;
6741     }
6742     return FALSE;
6743 }
6744
6745 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6746
6747 int CanPromote(ChessSquare piece, int y)
6748 {
6749         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6750         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6751         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6752            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6753            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6754                                                   gameInfo.variant == VariantMakruk) return FALSE;
6755         return (piece == BlackPawn && y == 1 ||
6756                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6757                 piece == BlackLance && y == 1 ||
6758                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6759 }
6760
6761 void LeftClick(ClickType clickType, int xPix, int yPix)
6762 {
6763     int x, y;
6764     Boolean saveAnimate;
6765     static int second = 0, promotionChoice = 0, clearFlag = 0;
6766     char promoChoice = NULLCHAR;
6767     ChessSquare piece;
6768
6769     if(appData.seekGraph && appData.icsActive && loggedOn &&
6770         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6771         SeekGraphClick(clickType, xPix, yPix, 0);
6772         return;
6773     }
6774
6775     if (clickType == Press) ErrorPopDown();
6776     MarkTargetSquares(1);
6777
6778     x = EventToSquare(xPix, BOARD_WIDTH);
6779     y = EventToSquare(yPix, BOARD_HEIGHT);
6780     if (!flipView && y >= 0) {
6781         y = BOARD_HEIGHT - 1 - y;
6782     }
6783     if (flipView && x >= 0) {
6784         x = BOARD_WIDTH - 1 - x;
6785     }
6786
6787     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6788         defaultPromoChoice = promoSweep;
6789         promoSweep = EmptySquare;   // terminate sweep
6790         promoDefaultAltered = TRUE;
6791         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6792     }
6793
6794     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6795         if(clickType == Release) return; // ignore upclick of click-click destination
6796         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6797         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6798         if(gameInfo.holdingsWidth &&
6799                 (WhiteOnMove(currentMove)
6800                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6801                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6802             // click in right holdings, for determining promotion piece
6803             ChessSquare p = boards[currentMove][y][x];
6804             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6805             if(p != EmptySquare) {
6806                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6807                 fromX = fromY = -1;
6808                 return;
6809             }
6810         }
6811         DrawPosition(FALSE, boards[currentMove]);
6812         return;
6813     }
6814
6815     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6816     if(clickType == Press
6817             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6818               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6819               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6820         return;
6821
6822     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6823         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6824
6825     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6826         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6827                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6828         defaultPromoChoice = DefaultPromoChoice(side);
6829     }
6830
6831     autoQueen = appData.alwaysPromoteToQueen;
6832
6833     if (fromX == -1) {
6834       int originalY = y;
6835       gatingPiece = EmptySquare;
6836       if (clickType != Press) {
6837         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6838             DragPieceEnd(xPix, yPix); dragging = 0;
6839             DrawPosition(FALSE, NULL);
6840         }
6841         return;
6842       }
6843       fromX = x; fromY = y;
6844       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6845          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6846          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6847             /* First square */
6848             if (OKToStartUserMove(fromX, fromY)) {
6849                 second = 0;
6850                 MarkTargetSquares(0);
6851                 DragPieceBegin(xPix, yPix); dragging = 1;
6852                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6853                     promoSweep = defaultPromoChoice;
6854                     selectFlag = 0; lastX = xPix; lastY = yPix;
6855                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6856                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6857                 }
6858                 if (appData.highlightDragging) {
6859                     SetHighlights(fromX, fromY, -1, -1);
6860                 }
6861             } else fromX = fromY = -1;
6862             return;
6863         }
6864     }
6865
6866     /* fromX != -1 */
6867     if (clickType == Press && gameMode != EditPosition) {
6868         ChessSquare fromP;
6869         ChessSquare toP;
6870         int frc;
6871
6872         // ignore off-board to clicks
6873         if(y < 0 || x < 0) return;
6874
6875         /* Check if clicking again on the same color piece */
6876         fromP = boards[currentMove][fromY][fromX];
6877         toP = boards[currentMove][y][x];
6878         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6879         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6880              WhitePawn <= toP && toP <= WhiteKing &&
6881              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6882              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6883             (BlackPawn <= fromP && fromP <= BlackKing &&
6884              BlackPawn <= toP && toP <= BlackKing &&
6885              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6886              !(fromP == BlackKing && toP == BlackRook && frc))) {
6887             /* Clicked again on same color piece -- changed his mind */
6888             second = (x == fromX && y == fromY);
6889             promoDefaultAltered = FALSE;
6890            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6891             if (appData.highlightDragging) {
6892                 SetHighlights(x, y, -1, -1);
6893             } else {
6894                 ClearHighlights();
6895             }
6896             if (OKToStartUserMove(x, y)) {
6897                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6898                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6899                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6900                  gatingPiece = boards[currentMove][fromY][fromX];
6901                 else gatingPiece = EmptySquare;
6902                 fromX = x;
6903                 fromY = y; dragging = 1;
6904                 MarkTargetSquares(0);
6905                 DragPieceBegin(xPix, yPix);
6906                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6907                     promoSweep = defaultPromoChoice;
6908                     selectFlag = 0; lastX = xPix; lastY = yPix;
6909                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6910                 }
6911             }
6912            }
6913            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6914            second = FALSE; 
6915         }
6916         // ignore clicks on holdings
6917         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6918     }
6919
6920     if (clickType == Release && x == fromX && y == fromY) {
6921         DragPieceEnd(xPix, yPix); dragging = 0;
6922         if(clearFlag) {
6923             // a deferred attempt to click-click move an empty square on top of a piece
6924             boards[currentMove][y][x] = EmptySquare;
6925             ClearHighlights();
6926             DrawPosition(FALSE, boards[currentMove]);
6927             fromX = fromY = -1; clearFlag = 0;
6928             return;
6929         }
6930         if (appData.animateDragging) {
6931             /* Undo animation damage if any */
6932             DrawPosition(FALSE, NULL);
6933         }
6934         if (second) {
6935             /* Second up/down in same square; just abort move */
6936             second = 0;
6937             fromX = fromY = -1;
6938             gatingPiece = EmptySquare;
6939             ClearHighlights();
6940             gotPremove = 0;
6941             ClearPremoveHighlights();
6942         } else {
6943             /* First upclick in same square; start click-click mode */
6944             SetHighlights(x, y, -1, -1);
6945         }
6946         return;
6947     }
6948
6949     clearFlag = 0;
6950
6951     /* we now have a different from- and (possibly off-board) to-square */
6952     /* Completed move */
6953     toX = x;
6954     toY = y;
6955     saveAnimate = appData.animate;
6956     if (clickType == Press) {
6957         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6958             // must be Edit Position mode with empty-square selected
6959             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6960             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6961             return;
6962         }
6963         /* Finish clickclick move */
6964         if (appData.animate || appData.highlightLastMove) {
6965             SetHighlights(fromX, fromY, toX, toY);
6966         } else {
6967             ClearHighlights();
6968         }
6969     } else {
6970         /* Finish drag move */
6971         if (appData.highlightLastMove) {
6972             SetHighlights(fromX, fromY, toX, toY);
6973         } else {
6974             ClearHighlights();
6975         }
6976         DragPieceEnd(xPix, yPix); dragging = 0;
6977         /* Don't animate move and drag both */
6978         appData.animate = FALSE;
6979     }
6980
6981     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6982     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6983         ChessSquare piece = boards[currentMove][fromY][fromX];
6984         if(gameMode == EditPosition && piece != EmptySquare &&
6985            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6986             int n;
6987
6988             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6989                 n = PieceToNumber(piece - (int)BlackPawn);
6990                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6991                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6992                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6993             } else
6994             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6995                 n = PieceToNumber(piece);
6996                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6997                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6998                 boards[currentMove][n][BOARD_WIDTH-2]++;
6999             }
7000             boards[currentMove][fromY][fromX] = EmptySquare;
7001         }
7002         ClearHighlights();
7003         fromX = fromY = -1;
7004         DrawPosition(TRUE, boards[currentMove]);
7005         return;
7006     }
7007
7008     // off-board moves should not be highlighted
7009     if(x < 0 || y < 0) ClearHighlights();
7010
7011     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7012
7013     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
7014         SetHighlights(fromX, fromY, toX, toY);
7015         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7016             // [HGM] super: promotion to captured piece selected from holdings
7017             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7018             promotionChoice = TRUE;
7019             // kludge follows to temporarily execute move on display, without promoting yet
7020             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7021             boards[currentMove][toY][toX] = p;
7022             DrawPosition(FALSE, boards[currentMove]);
7023             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7024             boards[currentMove][toY][toX] = q;
7025             DisplayMessage("Click in holdings to choose piece", "");
7026             return;
7027         }
7028         PromotionPopUp();
7029     } else {
7030         int oldMove = currentMove;
7031         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7032         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7033         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7034         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7035            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7036             DrawPosition(TRUE, boards[currentMove]);
7037         fromX = fromY = -1;
7038     }
7039     appData.animate = saveAnimate;
7040     if (appData.animate || appData.animateDragging) {
7041         /* Undo animation damage if needed */
7042         DrawPosition(FALSE, NULL);
7043     }
7044 }
7045
7046 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7047 {   // front-end-free part taken out of PieceMenuPopup
7048     int whichMenu; int xSqr, ySqr;
7049
7050     if(seekGraphUp) { // [HGM] seekgraph
7051         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7052         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7053         return -2;
7054     }
7055
7056     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7057          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7058         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7059         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7060         if(action == Press)   {
7061             originalFlip = flipView;
7062             flipView = !flipView; // temporarily flip board to see game from partners perspective
7063             DrawPosition(TRUE, partnerBoard);
7064             DisplayMessage(partnerStatus, "");
7065             partnerUp = TRUE;
7066         } else if(action == Release) {
7067             flipView = originalFlip;
7068             DrawPosition(TRUE, boards[currentMove]);
7069             partnerUp = FALSE;
7070         }
7071         return -2;
7072     }
7073
7074     xSqr = EventToSquare(x, BOARD_WIDTH);
7075     ySqr = EventToSquare(y, BOARD_HEIGHT);
7076     if (action == Release) {
7077         if(pieceSweep != EmptySquare) {
7078             EditPositionMenuEvent(pieceSweep, toX, toY);
7079             pieceSweep = EmptySquare;
7080         } else UnLoadPV(); // [HGM] pv
7081     }
7082     if (action != Press) return -2; // return code to be ignored
7083     switch (gameMode) {
7084       case IcsExamining:
7085         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7086       case EditPosition:
7087         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7088         if (xSqr < 0 || ySqr < 0) return -1;
7089         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7090         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7091         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7092         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7093         NextPiece(0);
7094         return -2;
7095       case IcsObserving:
7096         if(!appData.icsEngineAnalyze) return -1;
7097       case IcsPlayingWhite:
7098       case IcsPlayingBlack:
7099         if(!appData.zippyPlay) goto noZip;
7100       case AnalyzeMode:
7101       case AnalyzeFile:
7102       case MachinePlaysWhite:
7103       case MachinePlaysBlack:
7104       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7105         if (!appData.dropMenu) {
7106           LoadPV(x, y);
7107           return 2; // flag front-end to grab mouse events
7108         }
7109         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7110            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7111       case EditGame:
7112       noZip:
7113         if (xSqr < 0 || ySqr < 0) return -1;
7114         if (!appData.dropMenu || appData.testLegality &&
7115             gameInfo.variant != VariantBughouse &&
7116             gameInfo.variant != VariantCrazyhouse) return -1;
7117         whichMenu = 1; // drop menu
7118         break;
7119       default:
7120         return -1;
7121     }
7122
7123     if (((*fromX = xSqr) < 0) ||
7124         ((*fromY = ySqr) < 0)) {
7125         *fromX = *fromY = -1;
7126         return -1;
7127     }
7128     if (flipView)
7129       *fromX = BOARD_WIDTH - 1 - *fromX;
7130     else
7131       *fromY = BOARD_HEIGHT - 1 - *fromY;
7132
7133     return whichMenu;
7134 }
7135
7136 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7137 {
7138 //    char * hint = lastHint;
7139     FrontEndProgramStats stats;
7140
7141     stats.which = cps == &first ? 0 : 1;
7142     stats.depth = cpstats->depth;
7143     stats.nodes = cpstats->nodes;
7144     stats.score = cpstats->score;
7145     stats.time = cpstats->time;
7146     stats.pv = cpstats->movelist;
7147     stats.hint = lastHint;
7148     stats.an_move_index = 0;
7149     stats.an_move_count = 0;
7150
7151     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7152         stats.hint = cpstats->move_name;
7153         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7154         stats.an_move_count = cpstats->nr_moves;
7155     }
7156
7157     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
7158
7159     SetProgramStats( &stats );
7160 }
7161
7162 void
7163 ClearEngineOutputPane(int which)
7164 {
7165     static FrontEndProgramStats dummyStats;
7166     dummyStats.which = which;
7167     dummyStats.pv = "#";
7168     SetProgramStats( &dummyStats );
7169 }
7170
7171 #define MAXPLAYERS 500
7172
7173 char *
7174 TourneyStandings(int display)
7175 {
7176     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7177     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7178     char result, *p, *names[MAXPLAYERS];
7179
7180     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7181         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7182     names[0] = p = strdup(appData.participants);
7183     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7184
7185     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7186
7187     while(result = appData.results[nr]) {
7188         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7189         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7190         wScore = bScore = 0;
7191         switch(result) {
7192           case '+': wScore = 2; break;
7193           case '-': bScore = 2; break;
7194           case '=': wScore = bScore = 1; break;
7195           case ' ':
7196           case '*': return strdup("busy"); // tourney not finished
7197         }
7198         score[w] += wScore;
7199         score[b] += bScore;
7200         games[w]++;
7201         games[b]++;
7202         nr++;
7203     }
7204     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7205     for(w=0; w<nPlayers; w++) {
7206         bScore = -1;
7207         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7208         ranking[w] = b; points[w] = bScore; score[b] = -2;
7209     }
7210     p = malloc(nPlayers*34+1);
7211     for(w=0; w<nPlayers && w<display; w++)
7212         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7213     free(names[0]);
7214     return p;
7215 }
7216
7217 void
7218 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7219 {       // count all piece types
7220         int p, f, r;
7221         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7222         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7223         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7224                 p = board[r][f];
7225                 pCnt[p]++;
7226                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7227                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7228                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7229                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7230                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7231                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7232         }
7233 }
7234
7235 int
7236 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7237 {
7238         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7239         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7240
7241         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7242         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7243         if(myPawns == 2 && nMine == 3) // KPP
7244             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7245         if(myPawns == 1 && nMine == 2) // KP
7246             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7247         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7248             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7249         if(myPawns) return FALSE;
7250         if(pCnt[WhiteRook+side])
7251             return pCnt[BlackRook-side] ||
7252                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7253                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7254                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7255         if(pCnt[WhiteCannon+side]) {
7256             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7257             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7258         }
7259         if(pCnt[WhiteKnight+side])
7260             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7261         return FALSE;
7262 }
7263
7264 int
7265 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7266 {
7267         VariantClass v = gameInfo.variant;
7268
7269         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7270         if(v == VariantShatranj) return TRUE; // always winnable through baring
7271         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7272         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7273
7274         if(v == VariantXiangqi) {
7275                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7276
7277                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7278                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7279                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7280                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7281                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7282                 if(stale) // we have at least one last-rank P plus perhaps C
7283                     return majors // KPKX
7284                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7285                 else // KCA*E*
7286                     return pCnt[WhiteFerz+side] // KCAK
7287                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7288                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7289                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7290
7291         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7292                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7293
7294                 if(nMine == 1) return FALSE; // bare King
7295                 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
7296                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7297                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7298                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7299                 if(pCnt[WhiteKnight+side])
7300                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7301                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7302                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7303                 if(nBishops)
7304                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7305                 if(pCnt[WhiteAlfil+side])
7306                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7307                 if(pCnt[WhiteWazir+side])
7308                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7309         }
7310
7311         return TRUE;
7312 }
7313
7314 int
7315 Adjudicate(ChessProgramState *cps)
7316 {       // [HGM] some adjudications useful with buggy engines
7317         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7318         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7319         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7320         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7321         int k, count = 0; static int bare = 1;
7322         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7323         Boolean canAdjudicate = !appData.icsActive;
7324
7325         // most tests only when we understand the game, i.e. legality-checking on
7326             if( appData.testLegality )
7327             {   /* [HGM] Some more adjudications for obstinate engines */
7328                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7329                 static int moveCount = 6;
7330                 ChessMove result;
7331                 char *reason = NULL;
7332
7333                 /* Count what is on board. */
7334                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7335
7336                 /* Some material-based adjudications that have to be made before stalemate test */
7337                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7338                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7339                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7340                      if(canAdjudicate && appData.checkMates) {
7341                          if(engineOpponent)
7342                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7343                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7344                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7345                          return 1;
7346                      }
7347                 }
7348
7349                 /* Bare King in Shatranj (loses) or Losers (wins) */
7350                 if( nrW == 1 || nrB == 1) {
7351                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7352                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7353                      if(canAdjudicate && appData.checkMates) {
7354                          if(engineOpponent)
7355                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7356                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7357                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7358                          return 1;
7359                      }
7360                   } else
7361                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7362                   {    /* bare King */
7363                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7364                         if(canAdjudicate && appData.checkMates) {
7365                             /* but only adjudicate if adjudication enabled */
7366                             if(engineOpponent)
7367                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7368                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7369                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7370                             return 1;
7371                         }
7372                   }
7373                 } else bare = 1;
7374
7375
7376             // don't wait for engine to announce game end if we can judge ourselves
7377             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7378               case MT_CHECK:
7379                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7380                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7381                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7382                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7383                             checkCnt++;
7384                         if(checkCnt >= 2) {
7385                             reason = "Xboard adjudication: 3rd check";
7386                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7387                             break;
7388                         }
7389                     }
7390                 }
7391               case MT_NONE:
7392               default:
7393                 break;
7394               case MT_STALEMATE:
7395               case MT_STAINMATE:
7396                 reason = "Xboard adjudication: Stalemate";
7397                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7398                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7399                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7400                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7401                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7402                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7403                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7404                                                                         EP_CHECKMATE : EP_WINS);
7405                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7406                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7407                 }
7408                 break;
7409               case MT_CHECKMATE:
7410                 reason = "Xboard adjudication: Checkmate";
7411                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7412                 break;
7413             }
7414
7415                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7416                     case EP_STALEMATE:
7417                         result = GameIsDrawn; break;
7418                     case EP_CHECKMATE:
7419                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7420                     case EP_WINS:
7421                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7422                     default:
7423                         result = EndOfFile;
7424                 }
7425                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7426                     if(engineOpponent)
7427                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7428                     GameEnds( result, reason, GE_XBOARD );
7429                     return 1;
7430                 }
7431
7432                 /* Next absolutely insufficient mating material. */
7433                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7434                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7435                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7436
7437                      /* always flag draws, for judging claims */
7438                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7439
7440                      if(canAdjudicate && appData.materialDraws) {
7441                          /* but only adjudicate them if adjudication enabled */
7442                          if(engineOpponent) {
7443                            SendToProgram("force\n", engineOpponent); // suppress reply
7444                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7445                          }
7446                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7447                          return 1;
7448                      }
7449                 }
7450
7451                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7452                 if(gameInfo.variant == VariantXiangqi ?
7453                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7454                  : nrW + nrB == 4 &&
7455                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7456                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7457                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7458                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7459                    ) ) {
7460                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7461                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7462                           if(engineOpponent) {
7463                             SendToProgram("force\n", engineOpponent); // suppress reply
7464                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7465                           }
7466                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7467                           return 1;
7468                      }
7469                 } else moveCount = 6;
7470             }
7471         if (appData.debugMode) { int i;
7472             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7473                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7474                     appData.drawRepeats);
7475             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7476               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7477
7478         }
7479
7480         // Repetition draws and 50-move rule can be applied independently of legality testing
7481
7482                 /* Check for rep-draws */
7483                 count = 0;
7484                 for(k = forwardMostMove-2;
7485                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7486                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7487                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7488                     k-=2)
7489                 {   int rights=0;
7490                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7491                         /* compare castling rights */
7492                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7493                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7494                                 rights++; /* King lost rights, while rook still had them */
7495                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7496                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7497                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7498                                    rights++; /* but at least one rook lost them */
7499                         }
7500                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7501                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7502                                 rights++;
7503                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7504                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7505                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7506                                    rights++;
7507                         }
7508                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7509                             && appData.drawRepeats > 1) {
7510                              /* adjudicate after user-specified nr of repeats */
7511                              int result = GameIsDrawn;
7512                              char *details = "XBoard adjudication: repetition draw";
7513                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7514                                 // [HGM] xiangqi: check for forbidden perpetuals
7515                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7516                                 for(m=forwardMostMove; m>k; m-=2) {
7517                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7518                                         ourPerpetual = 0; // the current mover did not always check
7519                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7520                                         hisPerpetual = 0; // the opponent did not always check
7521                                 }
7522                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7523                                                                         ourPerpetual, hisPerpetual);
7524                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7525                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7526                                     details = "Xboard adjudication: perpetual checking";
7527                                 } else
7528                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7529                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7530                                 } else
7531                                 // Now check for perpetual chases
7532                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7533                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7534                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7535                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7536                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7537                                         details = "Xboard adjudication: perpetual chasing";
7538                                     } else
7539                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7540                                         break; // Abort repetition-checking loop.
7541                                 }
7542                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7543                              }
7544                              if(engineOpponent) {
7545                                SendToProgram("force\n", engineOpponent); // suppress reply
7546                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7547                              }
7548                              GameEnds( result, details, GE_XBOARD );
7549                              return 1;
7550                         }
7551                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7552                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7553                     }
7554                 }
7555
7556                 /* Now we test for 50-move draws. Determine ply count */
7557                 count = forwardMostMove;
7558                 /* look for last irreversble move */
7559                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7560                     count--;
7561                 /* if we hit starting position, add initial plies */
7562                 if( count == backwardMostMove )
7563                     count -= initialRulePlies;
7564                 count = forwardMostMove - count;
7565                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7566                         // adjust reversible move counter for checks in Xiangqi
7567                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7568                         if(i < backwardMostMove) i = backwardMostMove;
7569                         while(i <= forwardMostMove) {
7570                                 lastCheck = inCheck; // check evasion does not count
7571                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7572                                 if(inCheck || lastCheck) count--; // check does not count
7573                                 i++;
7574                         }
7575                 }
7576                 if( count >= 100)
7577                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7578                          /* this is used to judge if draw claims are legal */
7579                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7580                          if(engineOpponent) {
7581                            SendToProgram("force\n", engineOpponent); // suppress reply
7582                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7583                          }
7584                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7585                          return 1;
7586                 }
7587
7588                 /* if draw offer is pending, treat it as a draw claim
7589                  * when draw condition present, to allow engines a way to
7590                  * claim draws before making their move to avoid a race
7591                  * condition occurring after their move
7592                  */
7593                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7594                          char *p = NULL;
7595                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7596                              p = "Draw claim: 50-move rule";
7597                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7598                              p = "Draw claim: 3-fold repetition";
7599                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7600                              p = "Draw claim: insufficient mating material";
7601                          if( p != NULL && canAdjudicate) {
7602                              if(engineOpponent) {
7603                                SendToProgram("force\n", engineOpponent); // suppress reply
7604                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7605                              }
7606                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7607                              return 1;
7608                          }
7609                 }
7610
7611                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7612                     if(engineOpponent) {
7613                       SendToProgram("force\n", engineOpponent); // suppress reply
7614                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7615                     }
7616                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7617                     return 1;
7618                 }
7619         return 0;
7620 }
7621
7622 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7623 {   // [HGM] book: this routine intercepts moves to simulate book replies
7624     char *bookHit = NULL;
7625
7626     //first determine if the incoming move brings opponent into his book
7627     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7628         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7629     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7630     if(bookHit != NULL && !cps->bookSuspend) {
7631         // make sure opponent is not going to reply after receiving move to book position
7632         SendToProgram("force\n", cps);
7633         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7634     }
7635     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7636     // now arrange restart after book miss
7637     if(bookHit) {
7638         // after a book hit we never send 'go', and the code after the call to this routine
7639         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7640         char buf[MSG_SIZ], *move = bookHit;
7641         if(cps->useSAN) {
7642             int fromX, fromY, toX, toY;
7643             char promoChar;
7644             ChessMove moveType;
7645             move = buf + 30;
7646             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7647                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7648                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7649                                     PosFlags(forwardMostMove),
7650                                     fromY, fromX, toY, toX, promoChar, move);
7651             } else {
7652                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7653                 bookHit = NULL;
7654             }
7655         }
7656         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7657         SendToProgram(buf, cps);
7658         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7659     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7660         SendToProgram("go\n", cps);
7661         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7662     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7663         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7664             SendToProgram("go\n", cps);
7665         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7666     }
7667     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7668 }
7669
7670 char *savedMessage;
7671 ChessProgramState *savedState;
7672 void DeferredBookMove(void)
7673 {
7674         if(savedState->lastPing != savedState->lastPong)
7675                     ScheduleDelayedEvent(DeferredBookMove, 10);
7676         else
7677         HandleMachineMove(savedMessage, savedState);
7678 }
7679
7680 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7681
7682 void
7683 HandleMachineMove(message, cps)
7684      char *message;
7685      ChessProgramState *cps;
7686 {
7687     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7688     char realname[MSG_SIZ];
7689     int fromX, fromY, toX, toY;
7690     ChessMove moveType;
7691     char promoChar;
7692     char *p, *pv=buf1;
7693     int machineWhite;
7694     char *bookHit;
7695
7696     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7697         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7698         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7699             DisplayError(_("Invalid pairing from pairing engine"), 0);
7700             return;
7701         }
7702         pairingReceived = 1;
7703         NextMatchGame();
7704         return; // Skim the pairing messages here.
7705     }
7706
7707     cps->userError = 0;
7708
7709 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7710     /*
7711      * Kludge to ignore BEL characters
7712      */
7713     while (*message == '\007') message++;
7714
7715     /*
7716      * [HGM] engine debug message: ignore lines starting with '#' character
7717      */
7718     if(cps->debug && *message == '#') return;
7719
7720     /*
7721      * Look for book output
7722      */
7723     if (cps == &first && bookRequested) {
7724         if (message[0] == '\t' || message[0] == ' ') {
7725             /* Part of the book output is here; append it */
7726             strcat(bookOutput, message);
7727             strcat(bookOutput, "  \n");
7728             return;
7729         } else if (bookOutput[0] != NULLCHAR) {
7730             /* All of book output has arrived; display it */
7731             char *p = bookOutput;
7732             while (*p != NULLCHAR) {
7733                 if (*p == '\t') *p = ' ';
7734                 p++;
7735             }
7736             DisplayInformation(bookOutput);
7737             bookRequested = FALSE;
7738             /* Fall through to parse the current output */
7739         }
7740     }
7741
7742     /*
7743      * Look for machine move.
7744      */
7745     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7746         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7747     {
7748         /* This method is only useful on engines that support ping */
7749         if (cps->lastPing != cps->lastPong) {
7750           if (gameMode == BeginningOfGame) {
7751             /* Extra move from before last new; ignore */
7752             if (appData.debugMode) {
7753                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7754             }
7755           } else {
7756             if (appData.debugMode) {
7757                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7758                         cps->which, gameMode);
7759             }
7760
7761             SendToProgram("undo\n", cps);
7762           }
7763           return;
7764         }
7765
7766         switch (gameMode) {
7767           case BeginningOfGame:
7768             /* Extra move from before last reset; ignore */
7769             if (appData.debugMode) {
7770                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7771             }
7772             return;
7773
7774           case EndOfGame:
7775           case IcsIdle:
7776           default:
7777             /* Extra move after we tried to stop.  The mode test is
7778                not a reliable way of detecting this problem, but it's
7779                the best we can do on engines that don't support ping.
7780             */
7781             if (appData.debugMode) {
7782                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7783                         cps->which, gameMode);
7784             }
7785             SendToProgram("undo\n", cps);
7786             return;
7787
7788           case MachinePlaysWhite:
7789           case IcsPlayingWhite:
7790             machineWhite = TRUE;
7791             break;
7792
7793           case MachinePlaysBlack:
7794           case IcsPlayingBlack:
7795             machineWhite = FALSE;
7796             break;
7797
7798           case TwoMachinesPlay:
7799             machineWhite = (cps->twoMachinesColor[0] == 'w');
7800             break;
7801         }
7802         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7803             if (appData.debugMode) {
7804                 fprintf(debugFP,
7805                         "Ignoring move out of turn by %s, gameMode %d"
7806                         ", forwardMost %d\n",
7807                         cps->which, gameMode, forwardMostMove);
7808             }
7809             return;
7810         }
7811
7812     if (appData.debugMode) { int f = forwardMostMove;
7813         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7814                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7815                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7816     }
7817         if(cps->alphaRank) AlphaRank(machineMove, 4);
7818         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7819                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7820             /* Machine move could not be parsed; ignore it. */
7821           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7822                     machineMove, _(cps->which));
7823             DisplayError(buf1, 0);
7824             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7825                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7826             if (gameMode == TwoMachinesPlay) {
7827               GameEnds(machineWhite ? BlackWins : WhiteWins,
7828                        buf1, GE_XBOARD);
7829             }
7830             return;
7831         }
7832
7833         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7834         /* So we have to redo legality test with true e.p. status here,  */
7835         /* to make sure an illegal e.p. capture does not slip through,   */
7836         /* to cause a forfeit on a justified illegal-move complaint      */
7837         /* of the opponent.                                              */
7838         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7839            ChessMove moveType;
7840            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7841                              fromY, fromX, toY, toX, promoChar);
7842             if (appData.debugMode) {
7843                 int i;
7844                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7845                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7846                 fprintf(debugFP, "castling rights\n");
7847             }
7848             if(moveType == IllegalMove) {
7849               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7850                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7851                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7852                            buf1, GE_XBOARD);
7853                 return;
7854            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7855            /* [HGM] Kludge to handle engines that send FRC-style castling
7856               when they shouldn't (like TSCP-Gothic) */
7857            switch(moveType) {
7858              case WhiteASideCastleFR:
7859              case BlackASideCastleFR:
7860                toX+=2;
7861                currentMoveString[2]++;
7862                break;
7863              case WhiteHSideCastleFR:
7864              case BlackHSideCastleFR:
7865                toX--;
7866                currentMoveString[2]--;
7867                break;
7868              default: ; // nothing to do, but suppresses warning of pedantic compilers
7869            }
7870         }
7871         hintRequested = FALSE;
7872         lastHint[0] = NULLCHAR;
7873         bookRequested = FALSE;
7874         /* Program may be pondering now */
7875         cps->maybeThinking = TRUE;
7876         if (cps->sendTime == 2) cps->sendTime = 1;
7877         if (cps->offeredDraw) cps->offeredDraw--;
7878
7879         /* [AS] Save move info*/
7880         pvInfoList[ forwardMostMove ].score = programStats.score;
7881         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7882         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7883
7884         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7885
7886         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7887         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7888             int count = 0;
7889
7890             while( count < adjudicateLossPlies ) {
7891                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7892
7893                 if( count & 1 ) {
7894                     score = -score; /* Flip score for winning side */
7895                 }
7896
7897                 if( score > adjudicateLossThreshold ) {
7898                     break;
7899                 }
7900
7901                 count++;
7902             }
7903
7904             if( count >= adjudicateLossPlies ) {
7905                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7906
7907                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7908                     "Xboard adjudication",
7909                     GE_XBOARD );
7910
7911                 return;
7912             }
7913         }
7914
7915         if(Adjudicate(cps)) {
7916             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7917             return; // [HGM] adjudicate: for all automatic game ends
7918         }
7919
7920 #if ZIPPY
7921         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7922             first.initDone) {
7923           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7924                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7925                 SendToICS("draw ");
7926                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7927           }
7928           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7929           ics_user_moved = 1;
7930           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7931                 char buf[3*MSG_SIZ];
7932
7933                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7934                         programStats.score / 100.,
7935                         programStats.depth,
7936                         programStats.time / 100.,
7937                         (unsigned int)programStats.nodes,
7938                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7939                         programStats.movelist);
7940                 SendToICS(buf);
7941 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7942           }
7943         }
7944 #endif
7945
7946         /* [AS] Clear stats for next move */
7947         ClearProgramStats();
7948         thinkOutput[0] = NULLCHAR;
7949         hiddenThinkOutputState = 0;
7950
7951         bookHit = NULL;
7952         if (gameMode == TwoMachinesPlay) {
7953             /* [HGM] relaying draw offers moved to after reception of move */
7954             /* and interpreting offer as claim if it brings draw condition */
7955             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7956                 SendToProgram("draw\n", cps->other);
7957             }
7958             if (cps->other->sendTime) {
7959                 SendTimeRemaining(cps->other,
7960                                   cps->other->twoMachinesColor[0] == 'w');
7961             }
7962             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7963             if (firstMove && !bookHit) {
7964                 firstMove = FALSE;
7965                 if (cps->other->useColors) {
7966                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7967                 }
7968                 SendToProgram("go\n", cps->other);
7969             }
7970             cps->other->maybeThinking = TRUE;
7971         }
7972
7973         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7974
7975         if (!pausing && appData.ringBellAfterMoves) {
7976             RingBell();
7977         }
7978
7979         /*
7980          * Reenable menu items that were disabled while
7981          * machine was thinking
7982          */
7983         if (gameMode != TwoMachinesPlay)
7984             SetUserThinkingEnables();
7985
7986         // [HGM] book: after book hit opponent has received move and is now in force mode
7987         // force the book reply into it, and then fake that it outputted this move by jumping
7988         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7989         if(bookHit) {
7990                 static char bookMove[MSG_SIZ]; // a bit generous?
7991
7992                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7993                 strcat(bookMove, bookHit);
7994                 message = bookMove;
7995                 cps = cps->other;
7996                 programStats.nodes = programStats.depth = programStats.time =
7997                 programStats.score = programStats.got_only_move = 0;
7998                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7999
8000                 if(cps->lastPing != cps->lastPong) {
8001                     savedMessage = message; // args for deferred call
8002                     savedState = cps;
8003                     ScheduleDelayedEvent(DeferredBookMove, 10);
8004                     return;
8005                 }
8006                 goto FakeBookMove;
8007         }
8008
8009         return;
8010     }
8011
8012     /* Set special modes for chess engines.  Later something general
8013      *  could be added here; for now there is just one kludge feature,
8014      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8015      *  when "xboard" is given as an interactive command.
8016      */
8017     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8018         cps->useSigint = FALSE;
8019         cps->useSigterm = FALSE;
8020     }
8021     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8022       ParseFeatures(message+8, cps);
8023       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8024     }
8025
8026     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8027       int dummy, s=6; char buf[MSG_SIZ];
8028       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8029       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8030       ParseFEN(boards[0], &dummy, message+s);
8031       DrawPosition(TRUE, boards[0]);
8032       startedFromSetupPosition = TRUE;
8033       return;
8034     }
8035     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8036      * want this, I was asked to put it in, and obliged.
8037      */
8038     if (!strncmp(message, "setboard ", 9)) {
8039         Board initial_position;
8040
8041         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8042
8043         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8044             DisplayError(_("Bad FEN received from engine"), 0);
8045             return ;
8046         } else {
8047            Reset(TRUE, FALSE);
8048            CopyBoard(boards[0], initial_position);
8049            initialRulePlies = FENrulePlies;
8050            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8051            else gameMode = MachinePlaysBlack;
8052            DrawPosition(FALSE, boards[currentMove]);
8053         }
8054         return;
8055     }
8056
8057     /*
8058      * Look for communication commands
8059      */
8060     if (!strncmp(message, "telluser ", 9)) {
8061         if(message[9] == '\\' && message[10] == '\\')
8062             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8063         PlayTellSound();
8064         DisplayNote(message + 9);
8065         return;
8066     }
8067     if (!strncmp(message, "tellusererror ", 14)) {
8068         cps->userError = 1;
8069         if(message[14] == '\\' && message[15] == '\\')
8070             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8071         PlayTellSound();
8072         DisplayError(message + 14, 0);
8073         return;
8074     }
8075     if (!strncmp(message, "tellopponent ", 13)) {
8076       if (appData.icsActive) {
8077         if (loggedOn) {
8078           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8079           SendToICS(buf1);
8080         }
8081       } else {
8082         DisplayNote(message + 13);
8083       }
8084       return;
8085     }
8086     if (!strncmp(message, "tellothers ", 11)) {
8087       if (appData.icsActive) {
8088         if (loggedOn) {
8089           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8090           SendToICS(buf1);
8091         }
8092       }
8093       return;
8094     }
8095     if (!strncmp(message, "tellall ", 8)) {
8096       if (appData.icsActive) {
8097         if (loggedOn) {
8098           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8099           SendToICS(buf1);
8100         }
8101       } else {
8102         DisplayNote(message + 8);
8103       }
8104       return;
8105     }
8106     if (strncmp(message, "warning", 7) == 0) {
8107         /* Undocumented feature, use tellusererror in new code */
8108         DisplayError(message, 0);
8109         return;
8110     }
8111     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8112         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8113         strcat(realname, " query");
8114         AskQuestion(realname, buf2, buf1, cps->pr);
8115         return;
8116     }
8117     /* Commands from the engine directly to ICS.  We don't allow these to be
8118      *  sent until we are logged on. Crafty kibitzes have been known to
8119      *  interfere with the login process.
8120      */
8121     if (loggedOn) {
8122         if (!strncmp(message, "tellics ", 8)) {
8123             SendToICS(message + 8);
8124             SendToICS("\n");
8125             return;
8126         }
8127         if (!strncmp(message, "tellicsnoalias ", 15)) {
8128             SendToICS(ics_prefix);
8129             SendToICS(message + 15);
8130             SendToICS("\n");
8131             return;
8132         }
8133         /* The following are for backward compatibility only */
8134         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8135             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8136             SendToICS(ics_prefix);
8137             SendToICS(message);
8138             SendToICS("\n");
8139             return;
8140         }
8141     }
8142     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8143         return;
8144     }
8145     /*
8146      * If the move is illegal, cancel it and redraw the board.
8147      * Also deal with other error cases.  Matching is rather loose
8148      * here to accommodate engines written before the spec.
8149      */
8150     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8151         strncmp(message, "Error", 5) == 0) {
8152         if (StrStr(message, "name") ||
8153             StrStr(message, "rating") || StrStr(message, "?") ||
8154             StrStr(message, "result") || StrStr(message, "board") ||
8155             StrStr(message, "bk") || StrStr(message, "computer") ||
8156             StrStr(message, "variant") || StrStr(message, "hint") ||
8157             StrStr(message, "random") || StrStr(message, "depth") ||
8158             StrStr(message, "accepted")) {
8159             return;
8160         }
8161         if (StrStr(message, "protover")) {
8162           /* Program is responding to input, so it's apparently done
8163              initializing, and this error message indicates it is
8164              protocol version 1.  So we don't need to wait any longer
8165              for it to initialize and send feature commands. */
8166           FeatureDone(cps, 1);
8167           cps->protocolVersion = 1;
8168           return;
8169         }
8170         cps->maybeThinking = FALSE;
8171
8172         if (StrStr(message, "draw")) {
8173             /* Program doesn't have "draw" command */
8174             cps->sendDrawOffers = 0;
8175             return;
8176         }
8177         if (cps->sendTime != 1 &&
8178             (StrStr(message, "time") || StrStr(message, "otim"))) {
8179           /* Program apparently doesn't have "time" or "otim" command */
8180           cps->sendTime = 0;
8181           return;
8182         }
8183         if (StrStr(message, "analyze")) {
8184             cps->analysisSupport = FALSE;
8185             cps->analyzing = FALSE;
8186             Reset(FALSE, TRUE);
8187             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8188             DisplayError(buf2, 0);
8189             return;
8190         }
8191         if (StrStr(message, "(no matching move)st")) {
8192           /* Special kludge for GNU Chess 4 only */
8193           cps->stKludge = TRUE;
8194           SendTimeControl(cps, movesPerSession, timeControl,
8195                           timeIncrement, appData.searchDepth,
8196                           searchTime);
8197           return;
8198         }
8199         if (StrStr(message, "(no matching move)sd")) {
8200           /* Special kludge for GNU Chess 4 only */
8201           cps->sdKludge = TRUE;
8202           SendTimeControl(cps, movesPerSession, timeControl,
8203                           timeIncrement, appData.searchDepth,
8204                           searchTime);
8205           return;
8206         }
8207         if (!StrStr(message, "llegal")) {
8208             return;
8209         }
8210         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8211             gameMode == IcsIdle) return;
8212         if (forwardMostMove <= backwardMostMove) return;
8213         if (pausing) PauseEvent();
8214       if(appData.forceIllegal) {
8215             // [HGM] illegal: machine refused move; force position after move into it
8216           SendToProgram("force\n", cps);
8217           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8218                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8219                 // when black is to move, while there might be nothing on a2 or black
8220                 // might already have the move. So send the board as if white has the move.
8221                 // But first we must change the stm of the engine, as it refused the last move
8222                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8223                 if(WhiteOnMove(forwardMostMove)) {
8224                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8225                     SendBoard(cps, forwardMostMove); // kludgeless board
8226                 } else {
8227                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8228                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8229                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8230                 }
8231           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8232             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8233                  gameMode == TwoMachinesPlay)
8234               SendToProgram("go\n", cps);
8235             return;
8236       } else
8237         if (gameMode == PlayFromGameFile) {
8238             /* Stop reading this game file */
8239             gameMode = EditGame;
8240             ModeHighlight();
8241         }
8242         /* [HGM] illegal-move claim should forfeit game when Xboard */
8243         /* only passes fully legal moves                            */
8244         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8245             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8246                                 "False illegal-move claim", GE_XBOARD );
8247             return; // do not take back move we tested as valid
8248         }
8249         currentMove = forwardMostMove-1;
8250         DisplayMove(currentMove-1); /* before DisplayMoveError */
8251         SwitchClocks(forwardMostMove-1); // [HGM] race
8252         DisplayBothClocks();
8253         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8254                 parseList[currentMove], _(cps->which));
8255         DisplayMoveError(buf1);
8256         DrawPosition(FALSE, boards[currentMove]);
8257         return;
8258     }
8259     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8260         /* Program has a broken "time" command that
8261            outputs a string not ending in newline.
8262            Don't use it. */
8263         cps->sendTime = 0;
8264     }
8265
8266     /*
8267      * If chess program startup fails, exit with an error message.
8268      * Attempts to recover here are futile.
8269      */
8270     if ((StrStr(message, "unknown host") != NULL)
8271         || (StrStr(message, "No remote directory") != NULL)
8272         || (StrStr(message, "not found") != NULL)
8273         || (StrStr(message, "No such file") != NULL)
8274         || (StrStr(message, "can't alloc") != NULL)
8275         || (StrStr(message, "Permission denied") != NULL)) {
8276
8277         cps->maybeThinking = FALSE;
8278         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8279                 _(cps->which), cps->program, cps->host, message);
8280         RemoveInputSource(cps->isr);
8281         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8282             if(cps == &first) appData.noChessProgram = TRUE;
8283             DisplayError(buf1, 0);
8284         }
8285         return;
8286     }
8287
8288     /*
8289      * Look for hint output
8290      */
8291     if (sscanf(message, "Hint: %s", buf1) == 1) {
8292         if (cps == &first && hintRequested) {
8293             hintRequested = FALSE;
8294             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8295                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8296                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8297                                     PosFlags(forwardMostMove),
8298                                     fromY, fromX, toY, toX, promoChar, buf1);
8299                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8300                 DisplayInformation(buf2);
8301             } else {
8302                 /* Hint move could not be parsed!? */
8303               snprintf(buf2, sizeof(buf2),
8304                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8305                         buf1, _(cps->which));
8306                 DisplayError(buf2, 0);
8307             }
8308         } else {
8309           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8310         }
8311         return;
8312     }
8313
8314     /*
8315      * Ignore other messages if game is not in progress
8316      */
8317     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8318         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8319
8320     /*
8321      * look for win, lose, draw, or draw offer
8322      */
8323     if (strncmp(message, "1-0", 3) == 0) {
8324         char *p, *q, *r = "";
8325         p = strchr(message, '{');
8326         if (p) {
8327             q = strchr(p, '}');
8328             if (q) {
8329                 *q = NULLCHAR;
8330                 r = p + 1;
8331             }
8332         }
8333         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8334         return;
8335     } else if (strncmp(message, "0-1", 3) == 0) {
8336         char *p, *q, *r = "";
8337         p = strchr(message, '{');
8338         if (p) {
8339             q = strchr(p, '}');
8340             if (q) {
8341                 *q = NULLCHAR;
8342                 r = p + 1;
8343             }
8344         }
8345         /* Kludge for Arasan 4.1 bug */
8346         if (strcmp(r, "Black resigns") == 0) {
8347             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8348             return;
8349         }
8350         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8351         return;
8352     } else if (strncmp(message, "1/2", 3) == 0) {
8353         char *p, *q, *r = "";
8354         p = strchr(message, '{');
8355         if (p) {
8356             q = strchr(p, '}');
8357             if (q) {
8358                 *q = NULLCHAR;
8359                 r = p + 1;
8360             }
8361         }
8362
8363         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8364         return;
8365
8366     } else if (strncmp(message, "White resign", 12) == 0) {
8367         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8368         return;
8369     } else if (strncmp(message, "Black resign", 12) == 0) {
8370         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8371         return;
8372     } else if (strncmp(message, "White matches", 13) == 0 ||
8373                strncmp(message, "Black matches", 13) == 0   ) {
8374         /* [HGM] ignore GNUShogi noises */
8375         return;
8376     } else if (strncmp(message, "White", 5) == 0 &&
8377                message[5] != '(' &&
8378                StrStr(message, "Black") == NULL) {
8379         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8380         return;
8381     } else if (strncmp(message, "Black", 5) == 0 &&
8382                message[5] != '(') {
8383         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8384         return;
8385     } else if (strcmp(message, "resign") == 0 ||
8386                strcmp(message, "computer resigns") == 0) {
8387         switch (gameMode) {
8388           case MachinePlaysBlack:
8389           case IcsPlayingBlack:
8390             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8391             break;
8392           case MachinePlaysWhite:
8393           case IcsPlayingWhite:
8394             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8395             break;
8396           case TwoMachinesPlay:
8397             if (cps->twoMachinesColor[0] == 'w')
8398               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8399             else
8400               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8401             break;
8402           default:
8403             /* can't happen */
8404             break;
8405         }
8406         return;
8407     } else if (strncmp(message, "opponent mates", 14) == 0) {
8408         switch (gameMode) {
8409           case MachinePlaysBlack:
8410           case IcsPlayingBlack:
8411             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8412             break;
8413           case MachinePlaysWhite:
8414           case IcsPlayingWhite:
8415             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8416             break;
8417           case TwoMachinesPlay:
8418             if (cps->twoMachinesColor[0] == 'w')
8419               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8420             else
8421               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8422             break;
8423           default:
8424             /* can't happen */
8425             break;
8426         }
8427         return;
8428     } else if (strncmp(message, "computer mates", 14) == 0) {
8429         switch (gameMode) {
8430           case MachinePlaysBlack:
8431           case IcsPlayingBlack:
8432             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8433             break;
8434           case MachinePlaysWhite:
8435           case IcsPlayingWhite:
8436             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8437             break;
8438           case TwoMachinesPlay:
8439             if (cps->twoMachinesColor[0] == 'w')
8440               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8441             else
8442               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8443             break;
8444           default:
8445             /* can't happen */
8446             break;
8447         }
8448         return;
8449     } else if (strncmp(message, "checkmate", 9) == 0) {
8450         if (WhiteOnMove(forwardMostMove)) {
8451             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8452         } else {
8453             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8454         }
8455         return;
8456     } else if (strstr(message, "Draw") != NULL ||
8457                strstr(message, "game is a draw") != NULL) {
8458         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8459         return;
8460     } else if (strstr(message, "offer") != NULL &&
8461                strstr(message, "draw") != NULL) {
8462 #if ZIPPY
8463         if (appData.zippyPlay && first.initDone) {
8464             /* Relay offer to ICS */
8465             SendToICS(ics_prefix);
8466             SendToICS("draw\n");
8467         }
8468 #endif
8469         cps->offeredDraw = 2; /* valid until this engine moves twice */
8470         if (gameMode == TwoMachinesPlay) {
8471             if (cps->other->offeredDraw) {
8472                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8473             /* [HGM] in two-machine mode we delay relaying draw offer      */
8474             /* until after we also have move, to see if it is really claim */
8475             }
8476         } else if (gameMode == MachinePlaysWhite ||
8477                    gameMode == MachinePlaysBlack) {
8478           if (userOfferedDraw) {
8479             DisplayInformation(_("Machine accepts your draw offer"));
8480             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8481           } else {
8482             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8483           }
8484         }
8485     }
8486
8487
8488     /*
8489      * Look for thinking output
8490      */
8491     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8492           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8493                                 ) {
8494         int plylev, mvleft, mvtot, curscore, time;
8495         char mvname[MOVE_LEN];
8496         u64 nodes; // [DM]
8497         char plyext;
8498         int ignore = FALSE;
8499         int prefixHint = FALSE;
8500         mvname[0] = NULLCHAR;
8501
8502         switch (gameMode) {
8503           case MachinePlaysBlack:
8504           case IcsPlayingBlack:
8505             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8506             break;
8507           case MachinePlaysWhite:
8508           case IcsPlayingWhite:
8509             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8510             break;
8511           case AnalyzeMode:
8512           case AnalyzeFile:
8513             break;
8514           case IcsObserving: /* [DM] icsEngineAnalyze */
8515             if (!appData.icsEngineAnalyze) ignore = TRUE;
8516             break;
8517           case TwoMachinesPlay:
8518             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8519                 ignore = TRUE;
8520             }
8521             break;
8522           default:
8523             ignore = TRUE;
8524             break;
8525         }
8526
8527         if (!ignore) {
8528             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8529             buf1[0] = NULLCHAR;
8530             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8531                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8532
8533                 if (plyext != ' ' && plyext != '\t') {
8534                     time *= 100;
8535                 }
8536
8537                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8538                 if( cps->scoreIsAbsolute &&
8539                     ( gameMode == MachinePlaysBlack ||
8540                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8541                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8542                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8543                      !WhiteOnMove(currentMove)
8544                     ) )
8545                 {
8546                     curscore = -curscore;
8547                 }
8548
8549                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8550
8551                 tempStats.depth = plylev;
8552                 tempStats.nodes = nodes;
8553                 tempStats.time = time;
8554                 tempStats.score = curscore;
8555                 tempStats.got_only_move = 0;
8556
8557                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8558                         int ticklen;
8559
8560                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8561                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8562                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8563                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8564                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8565                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8566                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8567                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8568                 }
8569
8570                 /* Buffer overflow protection */
8571                 if (pv[0] != NULLCHAR) {
8572                     if (strlen(pv) >= sizeof(tempStats.movelist)
8573                         && appData.debugMode) {
8574                         fprintf(debugFP,
8575                                 "PV is too long; using the first %u bytes.\n",
8576                                 (unsigned) sizeof(tempStats.movelist) - 1);
8577                     }
8578
8579                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8580                 } else {
8581                     sprintf(tempStats.movelist, " no PV\n");
8582                 }
8583
8584                 if (tempStats.seen_stat) {
8585                     tempStats.ok_to_send = 1;
8586                 }
8587
8588                 if (strchr(tempStats.movelist, '(') != NULL) {
8589                     tempStats.line_is_book = 1;
8590                     tempStats.nr_moves = 0;
8591                     tempStats.moves_left = 0;
8592                 } else {
8593                     tempStats.line_is_book = 0;
8594                 }
8595
8596                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8597                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8598
8599                 SendProgramStatsToFrontend( cps, &tempStats );
8600
8601                 /*
8602                     [AS] Protect the thinkOutput buffer from overflow... this
8603                     is only useful if buf1 hasn't overflowed first!
8604                 */
8605                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8606                          plylev,
8607                          (gameMode == TwoMachinesPlay ?
8608                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8609                          ((double) curscore) / 100.0,
8610                          prefixHint ? lastHint : "",
8611                          prefixHint ? " " : "" );
8612
8613                 if( buf1[0] != NULLCHAR ) {
8614                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8615
8616                     if( strlen(pv) > max_len ) {
8617                         if( appData.debugMode) {
8618                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8619                         }
8620                         pv[max_len+1] = '\0';
8621                     }
8622
8623                     strcat( thinkOutput, pv);
8624                 }
8625
8626                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8627                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8628                     DisplayMove(currentMove - 1);
8629                 }
8630                 return;
8631
8632             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8633                 /* crafty (9.25+) says "(only move) <move>"
8634                  * if there is only 1 legal move
8635                  */
8636                 sscanf(p, "(only move) %s", buf1);
8637                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8638                 sprintf(programStats.movelist, "%s (only move)", buf1);
8639                 programStats.depth = 1;
8640                 programStats.nr_moves = 1;
8641                 programStats.moves_left = 1;
8642                 programStats.nodes = 1;
8643                 programStats.time = 1;
8644                 programStats.got_only_move = 1;
8645
8646                 /* Not really, but we also use this member to
8647                    mean "line isn't going to change" (Crafty
8648                    isn't searching, so stats won't change) */
8649                 programStats.line_is_book = 1;
8650
8651                 SendProgramStatsToFrontend( cps, &programStats );
8652
8653                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8654                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8655                     DisplayMove(currentMove - 1);
8656                 }
8657                 return;
8658             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8659                               &time, &nodes, &plylev, &mvleft,
8660                               &mvtot, mvname) >= 5) {
8661                 /* The stat01: line is from Crafty (9.29+) in response
8662                    to the "." command */
8663                 programStats.seen_stat = 1;
8664                 cps->maybeThinking = TRUE;
8665
8666                 if (programStats.got_only_move || !appData.periodicUpdates)
8667                   return;
8668
8669                 programStats.depth = plylev;
8670                 programStats.time = time;
8671                 programStats.nodes = nodes;
8672                 programStats.moves_left = mvleft;
8673                 programStats.nr_moves = mvtot;
8674                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8675                 programStats.ok_to_send = 1;
8676                 programStats.movelist[0] = '\0';
8677
8678                 SendProgramStatsToFrontend( cps, &programStats );
8679
8680                 return;
8681
8682             } else if (strncmp(message,"++",2) == 0) {
8683                 /* Crafty 9.29+ outputs this */
8684                 programStats.got_fail = 2;
8685                 return;
8686
8687             } else if (strncmp(message,"--",2) == 0) {
8688                 /* Crafty 9.29+ outputs this */
8689                 programStats.got_fail = 1;
8690                 return;
8691
8692             } else if (thinkOutput[0] != NULLCHAR &&
8693                        strncmp(message, "    ", 4) == 0) {
8694                 unsigned message_len;
8695
8696                 p = message;
8697                 while (*p && *p == ' ') p++;
8698
8699                 message_len = strlen( p );
8700
8701                 /* [AS] Avoid buffer overflow */
8702                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8703                     strcat(thinkOutput, " ");
8704                     strcat(thinkOutput, p);
8705                 }
8706
8707                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8708                     strcat(programStats.movelist, " ");
8709                     strcat(programStats.movelist, p);
8710                 }
8711
8712                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8713                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8714                     DisplayMove(currentMove - 1);
8715                 }
8716                 return;
8717             }
8718         }
8719         else {
8720             buf1[0] = NULLCHAR;
8721
8722             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8723                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8724             {
8725                 ChessProgramStats cpstats;
8726
8727                 if (plyext != ' ' && plyext != '\t') {
8728                     time *= 100;
8729                 }
8730
8731                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8732                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8733                     curscore = -curscore;
8734                 }
8735
8736                 cpstats.depth = plylev;
8737                 cpstats.nodes = nodes;
8738                 cpstats.time = time;
8739                 cpstats.score = curscore;
8740                 cpstats.got_only_move = 0;
8741                 cpstats.movelist[0] = '\0';
8742
8743                 if (buf1[0] != NULLCHAR) {
8744                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8745                 }
8746
8747                 cpstats.ok_to_send = 0;
8748                 cpstats.line_is_book = 0;
8749                 cpstats.nr_moves = 0;
8750                 cpstats.moves_left = 0;
8751
8752                 SendProgramStatsToFrontend( cps, &cpstats );
8753             }
8754         }
8755     }
8756 }
8757
8758
8759 /* Parse a game score from the character string "game", and
8760    record it as the history of the current game.  The game
8761    score is NOT assumed to start from the standard position.
8762    The display is not updated in any way.
8763    */
8764 void
8765 ParseGameHistory(game)
8766      char *game;
8767 {
8768     ChessMove moveType;
8769     int fromX, fromY, toX, toY, boardIndex;
8770     char promoChar;
8771     char *p, *q;
8772     char buf[MSG_SIZ];
8773
8774     if (appData.debugMode)
8775       fprintf(debugFP, "Parsing game history: %s\n", game);
8776
8777     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8778     gameInfo.site = StrSave(appData.icsHost);
8779     gameInfo.date = PGNDate();
8780     gameInfo.round = StrSave("-");
8781
8782     /* Parse out names of players */
8783     while (*game == ' ') game++;
8784     p = buf;
8785     while (*game != ' ') *p++ = *game++;
8786     *p = NULLCHAR;
8787     gameInfo.white = StrSave(buf);
8788     while (*game == ' ') game++;
8789     p = buf;
8790     while (*game != ' ' && *game != '\n') *p++ = *game++;
8791     *p = NULLCHAR;
8792     gameInfo.black = StrSave(buf);
8793
8794     /* Parse moves */
8795     boardIndex = blackPlaysFirst ? 1 : 0;
8796     yynewstr(game);
8797     for (;;) {
8798         yyboardindex = boardIndex;
8799         moveType = (ChessMove) Myylex();
8800         switch (moveType) {
8801           case IllegalMove:             /* maybe suicide chess, etc. */
8802   if (appData.debugMode) {
8803     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8804     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8805     setbuf(debugFP, NULL);
8806   }
8807           case WhitePromotion:
8808           case BlackPromotion:
8809           case WhiteNonPromotion:
8810           case BlackNonPromotion:
8811           case NormalMove:
8812           case WhiteCapturesEnPassant:
8813           case BlackCapturesEnPassant:
8814           case WhiteKingSideCastle:
8815           case WhiteQueenSideCastle:
8816           case BlackKingSideCastle:
8817           case BlackQueenSideCastle:
8818           case WhiteKingSideCastleWild:
8819           case WhiteQueenSideCastleWild:
8820           case BlackKingSideCastleWild:
8821           case BlackQueenSideCastleWild:
8822           /* PUSH Fabien */
8823           case WhiteHSideCastleFR:
8824           case WhiteASideCastleFR:
8825           case BlackHSideCastleFR:
8826           case BlackASideCastleFR:
8827           /* POP Fabien */
8828             fromX = currentMoveString[0] - AAA;
8829             fromY = currentMoveString[1] - ONE;
8830             toX = currentMoveString[2] - AAA;
8831             toY = currentMoveString[3] - ONE;
8832             promoChar = currentMoveString[4];
8833             break;
8834           case WhiteDrop:
8835           case BlackDrop:
8836             fromX = moveType == WhiteDrop ?
8837               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8838             (int) CharToPiece(ToLower(currentMoveString[0]));
8839             fromY = DROP_RANK;
8840             toX = currentMoveString[2] - AAA;
8841             toY = currentMoveString[3] - ONE;
8842             promoChar = NULLCHAR;
8843             break;
8844           case AmbiguousMove:
8845             /* bug? */
8846             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8847   if (appData.debugMode) {
8848     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8849     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8850     setbuf(debugFP, NULL);
8851   }
8852             DisplayError(buf, 0);
8853             return;
8854           case ImpossibleMove:
8855             /* bug? */
8856             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8857   if (appData.debugMode) {
8858     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8859     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8860     setbuf(debugFP, NULL);
8861   }
8862             DisplayError(buf, 0);
8863             return;
8864           case EndOfFile:
8865             if (boardIndex < backwardMostMove) {
8866                 /* Oops, gap.  How did that happen? */
8867                 DisplayError(_("Gap in move list"), 0);
8868                 return;
8869             }
8870             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8871             if (boardIndex > forwardMostMove) {
8872                 forwardMostMove = boardIndex;
8873             }
8874             return;
8875           case ElapsedTime:
8876             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8877                 strcat(parseList[boardIndex-1], " ");
8878                 strcat(parseList[boardIndex-1], yy_text);
8879             }
8880             continue;
8881           case Comment:
8882           case PGNTag:
8883           case NAG:
8884           default:
8885             /* ignore */
8886             continue;
8887           case WhiteWins:
8888           case BlackWins:
8889           case GameIsDrawn:
8890           case GameUnfinished:
8891             if (gameMode == IcsExamining) {
8892                 if (boardIndex < backwardMostMove) {
8893                     /* Oops, gap.  How did that happen? */
8894                     return;
8895                 }
8896                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8897                 return;
8898             }
8899             gameInfo.result = moveType;
8900             p = strchr(yy_text, '{');
8901             if (p == NULL) p = strchr(yy_text, '(');
8902             if (p == NULL) {
8903                 p = yy_text;
8904                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8905             } else {
8906                 q = strchr(p, *p == '{' ? '}' : ')');
8907                 if (q != NULL) *q = NULLCHAR;
8908                 p++;
8909             }
8910             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8911             gameInfo.resultDetails = StrSave(p);
8912             continue;
8913         }
8914         if (boardIndex >= forwardMostMove &&
8915             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8916             backwardMostMove = blackPlaysFirst ? 1 : 0;
8917             return;
8918         }
8919         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8920                                  fromY, fromX, toY, toX, promoChar,
8921                                  parseList[boardIndex]);
8922         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8923         /* currentMoveString is set as a side-effect of yylex */
8924         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8925         strcat(moveList[boardIndex], "\n");
8926         boardIndex++;
8927         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8928         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8929           case MT_NONE:
8930           case MT_STALEMATE:
8931           default:
8932             break;
8933           case MT_CHECK:
8934             if(gameInfo.variant != VariantShogi)
8935                 strcat(parseList[boardIndex - 1], "+");
8936             break;
8937           case MT_CHECKMATE:
8938           case MT_STAINMATE:
8939             strcat(parseList[boardIndex - 1], "#");
8940             break;
8941         }
8942     }
8943 }
8944
8945
8946 /* Apply a move to the given board  */
8947 void
8948 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8949      int fromX, fromY, toX, toY;
8950      int promoChar;
8951      Board board;
8952 {
8953   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8954   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8955
8956     /* [HGM] compute & store e.p. status and castling rights for new position */
8957     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8958
8959       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8960       oldEP = (signed char)board[EP_STATUS];
8961       board[EP_STATUS] = EP_NONE;
8962
8963       if( board[toY][toX] != EmptySquare )
8964            board[EP_STATUS] = EP_CAPTURE;
8965
8966   if (fromY == DROP_RANK) {
8967         /* must be first */
8968         piece = board[toY][toX] = (ChessSquare) fromX;
8969   } else {
8970       int i;
8971
8972       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8973            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8974                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8975       } else
8976       if( board[fromY][fromX] == WhitePawn ) {
8977            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8978                board[EP_STATUS] = EP_PAWN_MOVE;
8979            if( toY-fromY==2) {
8980                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8981                         gameInfo.variant != VariantBerolina || toX < fromX)
8982                       board[EP_STATUS] = toX | berolina;
8983                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8984                         gameInfo.variant != VariantBerolina || toX > fromX)
8985                       board[EP_STATUS] = toX;
8986            }
8987       } else
8988       if( board[fromY][fromX] == BlackPawn ) {
8989            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8990                board[EP_STATUS] = EP_PAWN_MOVE;
8991            if( toY-fromY== -2) {
8992                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8993                         gameInfo.variant != VariantBerolina || toX < fromX)
8994                       board[EP_STATUS] = toX | berolina;
8995                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8996                         gameInfo.variant != VariantBerolina || toX > fromX)
8997                       board[EP_STATUS] = toX;
8998            }
8999        }
9000
9001        for(i=0; i<nrCastlingRights; i++) {
9002            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9003               board[CASTLING][i] == toX   && castlingRank[i] == toY
9004              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9005        }
9006
9007      if (fromX == toX && fromY == toY) return;
9008
9009      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9010      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9011      if(gameInfo.variant == VariantKnightmate)
9012          king += (int) WhiteUnicorn - (int) WhiteKing;
9013
9014     /* Code added by Tord: */
9015     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9016     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9017         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9018       board[fromY][fromX] = EmptySquare;
9019       board[toY][toX] = EmptySquare;
9020       if((toX > fromX) != (piece == WhiteRook)) {
9021         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9022       } else {
9023         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9024       }
9025     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9026                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9027       board[fromY][fromX] = EmptySquare;
9028       board[toY][toX] = EmptySquare;
9029       if((toX > fromX) != (piece == BlackRook)) {
9030         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9031       } else {
9032         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9033       }
9034     /* End of code added by Tord */
9035
9036     } else if (board[fromY][fromX] == king
9037         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9038         && toY == fromY && toX > fromX+1) {
9039         board[fromY][fromX] = EmptySquare;
9040         board[toY][toX] = king;
9041         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9042         board[fromY][BOARD_RGHT-1] = EmptySquare;
9043     } else if (board[fromY][fromX] == king
9044         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9045                && toY == fromY && toX < fromX-1) {
9046         board[fromY][fromX] = EmptySquare;
9047         board[toY][toX] = king;
9048         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9049         board[fromY][BOARD_LEFT] = EmptySquare;
9050     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9051                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9052                && toY >= BOARD_HEIGHT-promoRank
9053                ) {
9054         /* white pawn promotion */
9055         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9056         if (board[toY][toX] == EmptySquare) {
9057             board[toY][toX] = WhiteQueen;
9058         }
9059         if(gameInfo.variant==VariantBughouse ||
9060            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9061             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9062         board[fromY][fromX] = EmptySquare;
9063     } else if ((fromY == BOARD_HEIGHT-4)
9064                && (toX != fromX)
9065                && gameInfo.variant != VariantXiangqi
9066                && gameInfo.variant != VariantBerolina
9067                && (board[fromY][fromX] == WhitePawn)
9068                && (board[toY][toX] == EmptySquare)) {
9069         board[fromY][fromX] = EmptySquare;
9070         board[toY][toX] = WhitePawn;
9071         captured = board[toY - 1][toX];
9072         board[toY - 1][toX] = EmptySquare;
9073     } else if ((fromY == BOARD_HEIGHT-4)
9074                && (toX == fromX)
9075                && gameInfo.variant == VariantBerolina
9076                && (board[fromY][fromX] == WhitePawn)
9077                && (board[toY][toX] == EmptySquare)) {
9078         board[fromY][fromX] = EmptySquare;
9079         board[toY][toX] = WhitePawn;
9080         if(oldEP & EP_BEROLIN_A) {
9081                 captured = board[fromY][fromX-1];
9082                 board[fromY][fromX-1] = EmptySquare;
9083         }else{  captured = board[fromY][fromX+1];
9084                 board[fromY][fromX+1] = EmptySquare;
9085         }
9086     } else if (board[fromY][fromX] == king
9087         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9088                && toY == fromY && toX > fromX+1) {
9089         board[fromY][fromX] = EmptySquare;
9090         board[toY][toX] = king;
9091         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9092         board[fromY][BOARD_RGHT-1] = EmptySquare;
9093     } else if (board[fromY][fromX] == king
9094         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9095                && toY == fromY && toX < fromX-1) {
9096         board[fromY][fromX] = EmptySquare;
9097         board[toY][toX] = king;
9098         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9099         board[fromY][BOARD_LEFT] = EmptySquare;
9100     } else if (fromY == 7 && fromX == 3
9101                && board[fromY][fromX] == BlackKing
9102                && toY == 7 && toX == 5) {
9103         board[fromY][fromX] = EmptySquare;
9104         board[toY][toX] = BlackKing;
9105         board[fromY][7] = EmptySquare;
9106         board[toY][4] = BlackRook;
9107     } else if (fromY == 7 && fromX == 3
9108                && board[fromY][fromX] == BlackKing
9109                && toY == 7 && toX == 1) {
9110         board[fromY][fromX] = EmptySquare;
9111         board[toY][toX] = BlackKing;
9112         board[fromY][0] = EmptySquare;
9113         board[toY][2] = BlackRook;
9114     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9115                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9116                && toY < promoRank
9117                ) {
9118         /* black pawn promotion */
9119         board[toY][toX] = CharToPiece(ToLower(promoChar));
9120         if (board[toY][toX] == EmptySquare) {
9121             board[toY][toX] = BlackQueen;
9122         }
9123         if(gameInfo.variant==VariantBughouse ||
9124            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9125             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9126         board[fromY][fromX] = EmptySquare;
9127     } else if ((fromY == 3)
9128                && (toX != fromX)
9129                && gameInfo.variant != VariantXiangqi
9130                && gameInfo.variant != VariantBerolina
9131                && (board[fromY][fromX] == BlackPawn)
9132                && (board[toY][toX] == EmptySquare)) {
9133         board[fromY][fromX] = EmptySquare;
9134         board[toY][toX] = BlackPawn;
9135         captured = board[toY + 1][toX];
9136         board[toY + 1][toX] = EmptySquare;
9137     } else if ((fromY == 3)
9138                && (toX == fromX)
9139                && gameInfo.variant == VariantBerolina
9140                && (board[fromY][fromX] == BlackPawn)
9141                && (board[toY][toX] == EmptySquare)) {
9142         board[fromY][fromX] = EmptySquare;
9143         board[toY][toX] = BlackPawn;
9144         if(oldEP & EP_BEROLIN_A) {
9145                 captured = board[fromY][fromX-1];
9146                 board[fromY][fromX-1] = EmptySquare;
9147         }else{  captured = board[fromY][fromX+1];
9148                 board[fromY][fromX+1] = EmptySquare;
9149         }
9150     } else {
9151         board[toY][toX] = board[fromY][fromX];
9152         board[fromY][fromX] = EmptySquare;
9153     }
9154   }
9155
9156     if (gameInfo.holdingsWidth != 0) {
9157
9158       /* !!A lot more code needs to be written to support holdings  */
9159       /* [HGM] OK, so I have written it. Holdings are stored in the */
9160       /* penultimate board files, so they are automaticlly stored   */
9161       /* in the game history.                                       */
9162       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9163                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9164         /* Delete from holdings, by decreasing count */
9165         /* and erasing image if necessary            */
9166         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9167         if(p < (int) BlackPawn) { /* white drop */
9168              p -= (int)WhitePawn;
9169                  p = PieceToNumber((ChessSquare)p);
9170              if(p >= gameInfo.holdingsSize) p = 0;
9171              if(--board[p][BOARD_WIDTH-2] <= 0)
9172                   board[p][BOARD_WIDTH-1] = EmptySquare;
9173              if((int)board[p][BOARD_WIDTH-2] < 0)
9174                         board[p][BOARD_WIDTH-2] = 0;
9175         } else {                  /* black drop */
9176              p -= (int)BlackPawn;
9177                  p = PieceToNumber((ChessSquare)p);
9178              if(p >= gameInfo.holdingsSize) p = 0;
9179              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9180                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9181              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9182                         board[BOARD_HEIGHT-1-p][1] = 0;
9183         }
9184       }
9185       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9186           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9187         /* [HGM] holdings: Add to holdings, if holdings exist */
9188         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9189                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9190                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9191         }
9192         p = (int) captured;
9193         if (p >= (int) BlackPawn) {
9194           p -= (int)BlackPawn;
9195           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9196                   /* in Shogi restore piece to its original  first */
9197                   captured = (ChessSquare) (DEMOTED captured);
9198                   p = DEMOTED p;
9199           }
9200           p = PieceToNumber((ChessSquare)p);
9201           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9202           board[p][BOARD_WIDTH-2]++;
9203           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9204         } else {
9205           p -= (int)WhitePawn;
9206           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9207                   captured = (ChessSquare) (DEMOTED captured);
9208                   p = DEMOTED p;
9209           }
9210           p = PieceToNumber((ChessSquare)p);
9211           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9212           board[BOARD_HEIGHT-1-p][1]++;
9213           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9214         }
9215       }
9216     } else if (gameInfo.variant == VariantAtomic) {
9217       if (captured != EmptySquare) {
9218         int y, x;
9219         for (y = toY-1; y <= toY+1; y++) {
9220           for (x = toX-1; x <= toX+1; x++) {
9221             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9222                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9223               board[y][x] = EmptySquare;
9224             }
9225           }
9226         }
9227         board[toY][toX] = EmptySquare;
9228       }
9229     }
9230     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9231         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9232     } else
9233     if(promoChar == '+') {
9234         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9235         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9236     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9237         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9238     }
9239     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9240                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9241         // [HGM] superchess: take promotion piece out of holdings
9242         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9243         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9244             if(!--board[k][BOARD_WIDTH-2])
9245                 board[k][BOARD_WIDTH-1] = EmptySquare;
9246         } else {
9247             if(!--board[BOARD_HEIGHT-1-k][1])
9248                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9249         }
9250     }
9251
9252 }
9253
9254 /* Updates forwardMostMove */
9255 void
9256 MakeMove(fromX, fromY, toX, toY, promoChar)
9257      int fromX, fromY, toX, toY;
9258      int promoChar;
9259 {
9260 //    forwardMostMove++; // [HGM] bare: moved downstream
9261
9262     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9263         int timeLeft; static int lastLoadFlag=0; int king, piece;
9264         piece = boards[forwardMostMove][fromY][fromX];
9265         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9266         if(gameInfo.variant == VariantKnightmate)
9267             king += (int) WhiteUnicorn - (int) WhiteKing;
9268         if(forwardMostMove == 0) {
9269             if(blackPlaysFirst)
9270                 fprintf(serverMoves, "%s;", second.tidy);
9271             fprintf(serverMoves, "%s;", first.tidy);
9272             if(!blackPlaysFirst)
9273                 fprintf(serverMoves, "%s;", second.tidy);
9274         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9275         lastLoadFlag = loadFlag;
9276         // print base move
9277         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9278         // print castling suffix
9279         if( toY == fromY && piece == king ) {
9280             if(toX-fromX > 1)
9281                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9282             if(fromX-toX >1)
9283                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9284         }
9285         // e.p. suffix
9286         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9287              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9288              boards[forwardMostMove][toY][toX] == EmptySquare
9289              && fromX != toX && fromY != toY)
9290                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9291         // promotion suffix
9292         if(promoChar != NULLCHAR)
9293                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9294         if(!loadFlag) {
9295             fprintf(serverMoves, "/%d/%d",
9296                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9297             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9298             else                      timeLeft = blackTimeRemaining/1000;
9299             fprintf(serverMoves, "/%d", timeLeft);
9300         }
9301         fflush(serverMoves);
9302     }
9303
9304     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9305       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9306                         0, 1);
9307       return;
9308     }
9309     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9310     if (commentList[forwardMostMove+1] != NULL) {
9311         free(commentList[forwardMostMove+1]);
9312         commentList[forwardMostMove+1] = NULL;
9313     }
9314     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9315     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9316     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9317     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9318     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9319     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9320     gameInfo.result = GameUnfinished;
9321     if (gameInfo.resultDetails != NULL) {
9322         free(gameInfo.resultDetails);
9323         gameInfo.resultDetails = NULL;
9324     }
9325     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9326                               moveList[forwardMostMove - 1]);
9327     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9328                              PosFlags(forwardMostMove - 1),
9329                              fromY, fromX, toY, toX, promoChar,
9330                              parseList[forwardMostMove - 1]);
9331     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9332       case MT_NONE:
9333       case MT_STALEMATE:
9334       default:
9335         break;
9336       case MT_CHECK:
9337         if(gameInfo.variant != VariantShogi)
9338             strcat(parseList[forwardMostMove - 1], "+");
9339         break;
9340       case MT_CHECKMATE:
9341       case MT_STAINMATE:
9342         strcat(parseList[forwardMostMove - 1], "#");
9343         break;
9344     }
9345     if (appData.debugMode) {
9346         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9347     }
9348
9349 }
9350
9351 /* Updates currentMove if not pausing */
9352 void
9353 ShowMove(fromX, fromY, toX, toY)
9354 {
9355     int instant = (gameMode == PlayFromGameFile) ?
9356         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9357     if(appData.noGUI) return;
9358     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9359         if (!instant) {
9360             if (forwardMostMove == currentMove + 1) {
9361                 AnimateMove(boards[forwardMostMove - 1],
9362                             fromX, fromY, toX, toY);
9363             }
9364             if (appData.highlightLastMove) {
9365                 SetHighlights(fromX, fromY, toX, toY);
9366             }
9367         }
9368         currentMove = forwardMostMove;
9369     }
9370
9371     if (instant) return;
9372
9373     DisplayMove(currentMove - 1);
9374     DrawPosition(FALSE, boards[currentMove]);
9375     DisplayBothClocks();
9376     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9377     DisplayBook(currentMove);
9378 }
9379
9380 void SendEgtPath(ChessProgramState *cps)
9381 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9382         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9383
9384         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9385
9386         while(*p) {
9387             char c, *q = name+1, *r, *s;
9388
9389             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9390             while(*p && *p != ',') *q++ = *p++;
9391             *q++ = ':'; *q = 0;
9392             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9393                 strcmp(name, ",nalimov:") == 0 ) {
9394                 // take nalimov path from the menu-changeable option first, if it is defined
9395               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9396                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9397             } else
9398             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9399                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9400                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9401                 s = r = StrStr(s, ":") + 1; // beginning of path info
9402                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9403                 c = *r; *r = 0;             // temporarily null-terminate path info
9404                     *--q = 0;               // strip of trailig ':' from name
9405                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9406                 *r = c;
9407                 SendToProgram(buf,cps);     // send egtbpath command for this format
9408             }
9409             if(*p == ',') p++; // read away comma to position for next format name
9410         }
9411 }
9412
9413 void
9414 InitChessProgram(cps, setup)
9415      ChessProgramState *cps;
9416      int setup; /* [HGM] needed to setup FRC opening position */
9417 {
9418     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9419     if (appData.noChessProgram) return;
9420     hintRequested = FALSE;
9421     bookRequested = FALSE;
9422
9423     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9424     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9425     if(cps->memSize) { /* [HGM] memory */
9426       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9427         SendToProgram(buf, cps);
9428     }
9429     SendEgtPath(cps); /* [HGM] EGT */
9430     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9431       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9432         SendToProgram(buf, cps);
9433     }
9434
9435     SendToProgram(cps->initString, cps);
9436     if (gameInfo.variant != VariantNormal &&
9437         gameInfo.variant != VariantLoadable
9438         /* [HGM] also send variant if board size non-standard */
9439         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9440                                             ) {
9441       char *v = VariantName(gameInfo.variant);
9442       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9443         /* [HGM] in protocol 1 we have to assume all variants valid */
9444         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9445         DisplayFatalError(buf, 0, 1);
9446         return;
9447       }
9448
9449       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9450       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9451       if( gameInfo.variant == VariantXiangqi )
9452            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9453       if( gameInfo.variant == VariantShogi )
9454            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9455       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9456            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9457       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9458           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9459            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9460       if( gameInfo.variant == VariantCourier )
9461            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9462       if( gameInfo.variant == VariantSuper )
9463            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9464       if( gameInfo.variant == VariantGreat )
9465            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9466       if( gameInfo.variant == VariantSChess )
9467            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9468
9469       if(overruled) {
9470         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9471                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9472            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9473            if(StrStr(cps->variants, b) == NULL) {
9474                // specific sized variant not known, check if general sizing allowed
9475                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9476                    if(StrStr(cps->variants, "boardsize") == NULL) {
9477                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9478                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9479                        DisplayFatalError(buf, 0, 1);
9480                        return;
9481                    }
9482                    /* [HGM] here we really should compare with the maximum supported board size */
9483                }
9484            }
9485       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9486       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9487       SendToProgram(buf, cps);
9488     }
9489     currentlyInitializedVariant = gameInfo.variant;
9490
9491     /* [HGM] send opening position in FRC to first engine */
9492     if(setup) {
9493           SendToProgram("force\n", cps);
9494           SendBoard(cps, 0);
9495           /* engine is now in force mode! Set flag to wake it up after first move. */
9496           setboardSpoiledMachineBlack = 1;
9497     }
9498
9499     if (cps->sendICS) {
9500       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9501       SendToProgram(buf, cps);
9502     }
9503     cps->maybeThinking = FALSE;
9504     cps->offeredDraw = 0;
9505     if (!appData.icsActive) {
9506         SendTimeControl(cps, movesPerSession, timeControl,
9507                         timeIncrement, appData.searchDepth,
9508                         searchTime);
9509     }
9510     if (appData.showThinking
9511         // [HGM] thinking: four options require thinking output to be sent
9512         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9513                                 ) {
9514         SendToProgram("post\n", cps);
9515     }
9516     SendToProgram("hard\n", cps);
9517     if (!appData.ponderNextMove) {
9518         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9519            it without being sure what state we are in first.  "hard"
9520            is not a toggle, so that one is OK.
9521          */
9522         SendToProgram("easy\n", cps);
9523     }
9524     if (cps->usePing) {
9525       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9526       SendToProgram(buf, cps);
9527     }
9528     cps->initDone = TRUE;
9529     ClearEngineOutputPane(cps == &second);
9530 }
9531
9532
9533 void
9534 StartChessProgram(cps)
9535      ChessProgramState *cps;
9536 {
9537     char buf[MSG_SIZ];
9538     int err;
9539
9540     if (appData.noChessProgram) return;
9541     cps->initDone = FALSE;
9542
9543     if (strcmp(cps->host, "localhost") == 0) {
9544         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9545     } else if (*appData.remoteShell == NULLCHAR) {
9546         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9547     } else {
9548         if (*appData.remoteUser == NULLCHAR) {
9549           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9550                     cps->program);
9551         } else {
9552           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9553                     cps->host, appData.remoteUser, cps->program);
9554         }
9555         err = StartChildProcess(buf, "", &cps->pr);
9556     }
9557
9558     if (err != 0) {
9559       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9560         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9561         if(cps != &first) return;
9562         appData.noChessProgram = TRUE;
9563         ThawUI();
9564         SetNCPMode();
9565 //      DisplayFatalError(buf, err, 1);
9566 //      cps->pr = NoProc;
9567 //      cps->isr = NULL;
9568         return;
9569     }
9570
9571     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9572     if (cps->protocolVersion > 1) {
9573       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9574       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9575       cps->comboCnt = 0;  //                and values of combo boxes
9576       SendToProgram(buf, cps);
9577     } else {
9578       SendToProgram("xboard\n", cps);
9579     }
9580 }
9581
9582 void
9583 TwoMachinesEventIfReady P((void))
9584 {
9585   static int curMess = 0;
9586   if (first.lastPing != first.lastPong) {
9587     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9588     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9589     return;
9590   }
9591   if (second.lastPing != second.lastPong) {
9592     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9593     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9594     return;
9595   }
9596   DisplayMessage("", ""); curMess = 0;
9597   ThawUI();
9598   TwoMachinesEvent();
9599 }
9600
9601 char *
9602 MakeName(char *template)
9603 {
9604     time_t clock;
9605     struct tm *tm;
9606     static char buf[MSG_SIZ];
9607     char *p = buf;
9608     int i;
9609
9610     clock = time((time_t *)NULL);
9611     tm = localtime(&clock);
9612
9613     while(*p++ = *template++) if(p[-1] == '%') {
9614         switch(*template++) {
9615           case 0:   *p = 0; return buf;
9616           case 'Y': i = tm->tm_year+1900; break;
9617           case 'y': i = tm->tm_year-100; break;
9618           case 'M': i = tm->tm_mon+1; break;
9619           case 'd': i = tm->tm_mday; break;
9620           case 'h': i = tm->tm_hour; break;
9621           case 'm': i = tm->tm_min; break;
9622           case 's': i = tm->tm_sec; break;
9623           default:  i = 0;
9624         }
9625         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9626     }
9627     return buf;
9628 }
9629
9630 int
9631 CountPlayers(char *p)
9632 {
9633     int n = 0;
9634     while(p = strchr(p, '\n')) p++, n++; // count participants
9635     return n;
9636 }
9637
9638 FILE *
9639 WriteTourneyFile(char *results)
9640 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9641     FILE *f = fopen(appData.tourneyFile, "w");
9642     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9643         // create a file with tournament description
9644         fprintf(f, "-participants {%s}\n", appData.participants);
9645         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9646         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9647         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9648         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9649         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9650         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9651         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9652         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9653         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9654         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9655         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9656         if(searchTime > 0)
9657                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9658         else {
9659                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9660                 fprintf(f, "-tc %s\n", appData.timeControl);
9661                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9662         }
9663         fprintf(f, "-results \"%s\"\n", results);
9664     }
9665     return f;
9666 }
9667
9668 int
9669 CreateTourney(char *name)
9670 {
9671         FILE *f;
9672         if(name[0] == NULLCHAR) {
9673             if(appData.participants[0])
9674                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9675             return 0;
9676         }
9677         f = fopen(name, "r");
9678         if(f) { // file exists
9679             ASSIGN(appData.tourneyFile, name);
9680             ParseArgsFromFile(f); // parse it
9681         } else {
9682             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9683             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9684                 DisplayError(_("Not enough participants"), 0);
9685                 return 0;
9686             }
9687             ASSIGN(appData.tourneyFile, name);
9688             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9689             if((f = WriteTourneyFile("")) == NULL) return 0;
9690         }
9691         fclose(f);
9692         appData.noChessProgram = FALSE;
9693         appData.clockMode = TRUE;
9694         SetGNUMode();
9695         return 1;
9696 }
9697
9698 #define MAXENGINES 1000
9699 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9700
9701 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9702 {
9703     char buf[MSG_SIZ], *p, *q;
9704     int i=1;
9705     while(*names) {
9706         p = names; q = buf;
9707         while(*p && *p != '\n') *q++ = *p++;
9708         *q = 0;
9709         if(engineList[i]) free(engineList[i]);
9710         engineList[i] = strdup(buf);
9711         if(*p == '\n') p++;
9712         TidyProgramName(engineList[i], "localhost", buf);
9713         if(engineMnemonic[i]) free(engineMnemonic[i]);
9714         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9715             strcat(buf, " (");
9716             sscanf(q + 8, "%s", buf + strlen(buf));
9717             strcat(buf, ")");
9718         }
9719         engineMnemonic[i] = strdup(buf);
9720         names = p; i++;
9721       if(i > MAXENGINES - 2) break;
9722     }
9723     engineList[i] = NULL;
9724 }
9725
9726 // following implemented as macro to avoid type limitations
9727 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9728
9729 void SwapEngines(int n)
9730 {   // swap settings for first engine and other engine (so far only some selected options)
9731     int h;
9732     char *p;
9733     if(n == 0) return;
9734     SWAP(directory, p)
9735     SWAP(chessProgram, p)
9736     SWAP(isUCI, h)
9737     SWAP(hasOwnBookUCI, h)
9738     SWAP(protocolVersion, h)
9739     SWAP(reuse, h)
9740     SWAP(scoreIsAbsolute, h)
9741     SWAP(timeOdds, h)
9742     SWAP(logo, p)
9743     SWAP(pgnName, p)
9744     SWAP(pvSAN, h)
9745 }
9746
9747 void
9748 SetPlayer(int player)
9749 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9750     int i;
9751     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9752     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9753     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9754     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9755     if(mnemonic[i]) {
9756         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9757         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9758         ParseArgsFromString(buf);
9759     }
9760     free(engineName);
9761 }
9762
9763 int
9764 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9765 {   // determine players from game number
9766     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9767
9768     if(appData.tourneyType == 0) {
9769         roundsPerCycle = (nPlayers - 1) | 1;
9770         pairingsPerRound = nPlayers / 2;
9771     } else if(appData.tourneyType > 0) {
9772         roundsPerCycle = nPlayers - appData.tourneyType;
9773         pairingsPerRound = appData.tourneyType;
9774     }
9775     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9776     gamesPerCycle = gamesPerRound * roundsPerCycle;
9777     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9778     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9779     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9780     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9781     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9782     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9783
9784     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9785     if(appData.roundSync) *syncInterval = gamesPerRound;
9786
9787     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9788
9789     if(appData.tourneyType == 0) {
9790         if(curPairing == (nPlayers-1)/2 ) {
9791             *whitePlayer = curRound;
9792             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9793         } else {
9794             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9795             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9796             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9797             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9798         }
9799     } else if(appData.tourneyType > 0) {
9800         *whitePlayer = curPairing;
9801         *blackPlayer = curRound + appData.tourneyType;
9802     }
9803
9804     // take care of white/black alternation per round. 
9805     // For cycles and games this is already taken care of by default, derived from matchGame!
9806     return curRound & 1;
9807 }
9808
9809 int
9810 NextTourneyGame(int nr, int *swapColors)
9811 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9812     char *p, *q;
9813     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9814     FILE *tf;
9815     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9816     tf = fopen(appData.tourneyFile, "r");
9817     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9818     ParseArgsFromFile(tf); fclose(tf);
9819     InitTimeControls(); // TC might be altered from tourney file
9820
9821     nPlayers = CountPlayers(appData.participants); // count participants
9822     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9823     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9824
9825     if(syncInterval) {
9826         p = q = appData.results;
9827         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9828         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9829             DisplayMessage(_("Waiting for other game(s)"),"");
9830             waitingForGame = TRUE;
9831             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9832             return 0;
9833         }
9834         waitingForGame = FALSE;
9835     }
9836
9837     if(appData.tourneyType < 0) {
9838         if(nr>=0 && !pairingReceived) {
9839             char buf[1<<16];
9840             if(pairing.pr == NoProc) {
9841                 if(!appData.pairingEngine[0]) {
9842                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9843                     return 0;
9844                 }
9845                 StartChessProgram(&pairing); // starts the pairing engine
9846             }
9847             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9848             SendToProgram(buf, &pairing);
9849             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9850             SendToProgram(buf, &pairing);
9851             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9852         }
9853         pairingReceived = 0;                              // ... so we continue here 
9854         *swapColors = 0;
9855         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9856         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9857         matchGame = 1; roundNr = nr / syncInterval + 1;
9858     }
9859
9860     if(first.pr != NoProc) return 1; // engines already loaded
9861
9862     // redefine engines, engine dir, etc.
9863     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9864     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9865     SwapEngines(1);
9866     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9867     SwapEngines(1);         // and make that valid for second engine by swapping
9868     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9869     InitEngine(&second, 1);
9870     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9871     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9872     return 1;
9873 }
9874
9875 void
9876 NextMatchGame()
9877 {   // performs game initialization that does not invoke engines, and then tries to start the game
9878     int firstWhite, swapColors = 0;
9879     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9880     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9881     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9882     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9883     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9884     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9885     Reset(FALSE, first.pr != NoProc);
9886     appData.noChessProgram = FALSE;
9887     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9888     TwoMachinesEvent();
9889 }
9890
9891 void UserAdjudicationEvent( int result )
9892 {
9893     ChessMove gameResult = GameIsDrawn;
9894
9895     if( result > 0 ) {
9896         gameResult = WhiteWins;
9897     }
9898     else if( result < 0 ) {
9899         gameResult = BlackWins;
9900     }
9901
9902     if( gameMode == TwoMachinesPlay ) {
9903         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9904     }
9905 }
9906
9907
9908 // [HGM] save: calculate checksum of game to make games easily identifiable
9909 int StringCheckSum(char *s)
9910 {
9911         int i = 0;
9912         if(s==NULL) return 0;
9913         while(*s) i = i*259 + *s++;
9914         return i;
9915 }
9916
9917 int GameCheckSum()
9918 {
9919         int i, sum=0;
9920         for(i=backwardMostMove; i<forwardMostMove; i++) {
9921                 sum += pvInfoList[i].depth;
9922                 sum += StringCheckSum(parseList[i]);
9923                 sum += StringCheckSum(commentList[i]);
9924                 sum *= 261;
9925         }
9926         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9927         return sum + StringCheckSum(commentList[i]);
9928 } // end of save patch
9929
9930 void
9931 GameEnds(result, resultDetails, whosays)
9932      ChessMove result;
9933      char *resultDetails;
9934      int whosays;
9935 {
9936     GameMode nextGameMode;
9937     int isIcsGame;
9938     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9939
9940     if(endingGame) return; /* [HGM] crash: forbid recursion */
9941     endingGame = 1;
9942     if(twoBoards) { // [HGM] dual: switch back to one board
9943         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9944         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9945     }
9946     if (appData.debugMode) {
9947       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9948               result, resultDetails ? resultDetails : "(null)", whosays);
9949     }
9950
9951     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9952
9953     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9954         /* If we are playing on ICS, the server decides when the
9955            game is over, but the engine can offer to draw, claim
9956            a draw, or resign.
9957          */
9958 #if ZIPPY
9959         if (appData.zippyPlay && first.initDone) {
9960             if (result == GameIsDrawn) {
9961                 /* In case draw still needs to be claimed */
9962                 SendToICS(ics_prefix);
9963                 SendToICS("draw\n");
9964             } else if (StrCaseStr(resultDetails, "resign")) {
9965                 SendToICS(ics_prefix);
9966                 SendToICS("resign\n");
9967             }
9968         }
9969 #endif
9970         endingGame = 0; /* [HGM] crash */
9971         return;
9972     }
9973
9974     /* If we're loading the game from a file, stop */
9975     if (whosays == GE_FILE) {
9976       (void) StopLoadGameTimer();
9977       gameFileFP = NULL;
9978     }
9979
9980     /* Cancel draw offers */
9981     first.offeredDraw = second.offeredDraw = 0;
9982
9983     /* If this is an ICS game, only ICS can really say it's done;
9984        if not, anyone can. */
9985     isIcsGame = (gameMode == IcsPlayingWhite ||
9986                  gameMode == IcsPlayingBlack ||
9987                  gameMode == IcsObserving    ||
9988                  gameMode == IcsExamining);
9989
9990     if (!isIcsGame || whosays == GE_ICS) {
9991         /* OK -- not an ICS game, or ICS said it was done */
9992         StopClocks();
9993         if (!isIcsGame && !appData.noChessProgram)
9994           SetUserThinkingEnables();
9995
9996         /* [HGM] if a machine claims the game end we verify this claim */
9997         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9998             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9999                 char claimer;
10000                 ChessMove trueResult = (ChessMove) -1;
10001
10002                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10003                                             first.twoMachinesColor[0] :
10004                                             second.twoMachinesColor[0] ;
10005
10006                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10007                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10008                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10009                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10010                 } else
10011                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10012                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10013                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10014                 } else
10015                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10016                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10017                 }
10018
10019                 // now verify win claims, but not in drop games, as we don't understand those yet
10020                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10021                                                  || gameInfo.variant == VariantGreat) &&
10022                     (result == WhiteWins && claimer == 'w' ||
10023                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10024                       if (appData.debugMode) {
10025                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10026                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10027                       }
10028                       if(result != trueResult) {
10029                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10030                               result = claimer == 'w' ? BlackWins : WhiteWins;
10031                               resultDetails = buf;
10032                       }
10033                 } else
10034                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10035                     && (forwardMostMove <= backwardMostMove ||
10036                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10037                         (claimer=='b')==(forwardMostMove&1))
10038                                                                                   ) {
10039                       /* [HGM] verify: draws that were not flagged are false claims */
10040                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10041                       result = claimer == 'w' ? BlackWins : WhiteWins;
10042                       resultDetails = buf;
10043                 }
10044                 /* (Claiming a loss is accepted no questions asked!) */
10045             }
10046             /* [HGM] bare: don't allow bare King to win */
10047             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10048                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10049                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10050                && result != GameIsDrawn)
10051             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10052                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10053                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10054                         if(p >= 0 && p <= (int)WhiteKing) k++;
10055                 }
10056                 if (appData.debugMode) {
10057                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10058                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10059                 }
10060                 if(k <= 1) {
10061                         result = GameIsDrawn;
10062                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10063                         resultDetails = buf;
10064                 }
10065             }
10066         }
10067
10068
10069         if(serverMoves != NULL && !loadFlag) { char c = '=';
10070             if(result==WhiteWins) c = '+';
10071             if(result==BlackWins) c = '-';
10072             if(resultDetails != NULL)
10073                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10074         }
10075         if (resultDetails != NULL) {
10076             gameInfo.result = result;
10077             gameInfo.resultDetails = StrSave(resultDetails);
10078
10079             /* display last move only if game was not loaded from file */
10080             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10081                 DisplayMove(currentMove - 1);
10082
10083             if (forwardMostMove != 0) {
10084                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10085                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10086                                                                 ) {
10087                     if (*appData.saveGameFile != NULLCHAR) {
10088                         SaveGameToFile(appData.saveGameFile, TRUE);
10089                     } else if (appData.autoSaveGames) {
10090                         AutoSaveGame();
10091                     }
10092                     if (*appData.savePositionFile != NULLCHAR) {
10093                         SavePositionToFile(appData.savePositionFile);
10094                     }
10095                 }
10096             }
10097
10098             /* Tell program how game ended in case it is learning */
10099             /* [HGM] Moved this to after saving the PGN, just in case */
10100             /* engine died and we got here through time loss. In that */
10101             /* case we will get a fatal error writing the pipe, which */
10102             /* would otherwise lose us the PGN.                       */
10103             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10104             /* output during GameEnds should never be fatal anymore   */
10105             if (gameMode == MachinePlaysWhite ||
10106                 gameMode == MachinePlaysBlack ||
10107                 gameMode == TwoMachinesPlay ||
10108                 gameMode == IcsPlayingWhite ||
10109                 gameMode == IcsPlayingBlack ||
10110                 gameMode == BeginningOfGame) {
10111                 char buf[MSG_SIZ];
10112                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10113                         resultDetails);
10114                 if (first.pr != NoProc) {
10115                     SendToProgram(buf, &first);
10116                 }
10117                 if (second.pr != NoProc &&
10118                     gameMode == TwoMachinesPlay) {
10119                     SendToProgram(buf, &second);
10120                 }
10121             }
10122         }
10123
10124         if (appData.icsActive) {
10125             if (appData.quietPlay &&
10126                 (gameMode == IcsPlayingWhite ||
10127                  gameMode == IcsPlayingBlack)) {
10128                 SendToICS(ics_prefix);
10129                 SendToICS("set shout 1\n");
10130             }
10131             nextGameMode = IcsIdle;
10132             ics_user_moved = FALSE;
10133             /* clean up premove.  It's ugly when the game has ended and the
10134              * premove highlights are still on the board.
10135              */
10136             if (gotPremove) {
10137               gotPremove = FALSE;
10138               ClearPremoveHighlights();
10139               DrawPosition(FALSE, boards[currentMove]);
10140             }
10141             if (whosays == GE_ICS) {
10142                 switch (result) {
10143                 case WhiteWins:
10144                     if (gameMode == IcsPlayingWhite)
10145                         PlayIcsWinSound();
10146                     else if(gameMode == IcsPlayingBlack)
10147                         PlayIcsLossSound();
10148                     break;
10149                 case BlackWins:
10150                     if (gameMode == IcsPlayingBlack)
10151                         PlayIcsWinSound();
10152                     else if(gameMode == IcsPlayingWhite)
10153                         PlayIcsLossSound();
10154                     break;
10155                 case GameIsDrawn:
10156                     PlayIcsDrawSound();
10157                     break;
10158                 default:
10159                     PlayIcsUnfinishedSound();
10160                 }
10161             }
10162         } else if (gameMode == EditGame ||
10163                    gameMode == PlayFromGameFile ||
10164                    gameMode == AnalyzeMode ||
10165                    gameMode == AnalyzeFile) {
10166             nextGameMode = gameMode;
10167         } else {
10168             nextGameMode = EndOfGame;
10169         }
10170         pausing = FALSE;
10171         ModeHighlight();
10172     } else {
10173         nextGameMode = gameMode;
10174     }
10175
10176     if (appData.noChessProgram) {
10177         gameMode = nextGameMode;
10178         ModeHighlight();
10179         endingGame = 0; /* [HGM] crash */
10180         return;
10181     }
10182
10183     if (first.reuse) {
10184         /* Put first chess program into idle state */
10185         if (first.pr != NoProc &&
10186             (gameMode == MachinePlaysWhite ||
10187              gameMode == MachinePlaysBlack ||
10188              gameMode == TwoMachinesPlay ||
10189              gameMode == IcsPlayingWhite ||
10190              gameMode == IcsPlayingBlack ||
10191              gameMode == BeginningOfGame)) {
10192             SendToProgram("force\n", &first);
10193             if (first.usePing) {
10194               char buf[MSG_SIZ];
10195               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10196               SendToProgram(buf, &first);
10197             }
10198         }
10199     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10200         /* Kill off first chess program */
10201         if (first.isr != NULL)
10202           RemoveInputSource(first.isr);
10203         first.isr = NULL;
10204
10205         if (first.pr != NoProc) {
10206             ExitAnalyzeMode();
10207             DoSleep( appData.delayBeforeQuit );
10208             SendToProgram("quit\n", &first);
10209             DoSleep( appData.delayAfterQuit );
10210             DestroyChildProcess(first.pr, first.useSigterm);
10211         }
10212         first.pr = NoProc;
10213     }
10214     if (second.reuse) {
10215         /* Put second chess program into idle state */
10216         if (second.pr != NoProc &&
10217             gameMode == TwoMachinesPlay) {
10218             SendToProgram("force\n", &second);
10219             if (second.usePing) {
10220               char buf[MSG_SIZ];
10221               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10222               SendToProgram(buf, &second);
10223             }
10224         }
10225     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10226         /* Kill off second chess program */
10227         if (second.isr != NULL)
10228           RemoveInputSource(second.isr);
10229         second.isr = NULL;
10230
10231         if (second.pr != NoProc) {
10232             DoSleep( appData.delayBeforeQuit );
10233             SendToProgram("quit\n", &second);
10234             DoSleep( appData.delayAfterQuit );
10235             DestroyChildProcess(second.pr, second.useSigterm);
10236         }
10237         second.pr = NoProc;
10238     }
10239
10240     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10241         char resChar = '=';
10242         switch (result) {
10243         case WhiteWins:
10244           resChar = '+';
10245           if (first.twoMachinesColor[0] == 'w') {
10246             first.matchWins++;
10247           } else {
10248             second.matchWins++;
10249           }
10250           break;
10251         case BlackWins:
10252           resChar = '-';
10253           if (first.twoMachinesColor[0] == 'b') {
10254             first.matchWins++;
10255           } else {
10256             second.matchWins++;
10257           }
10258           break;
10259         case GameUnfinished:
10260           resChar = ' ';
10261         default:
10262           break;
10263         }
10264
10265         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10266         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10267             ReserveGame(nextGame, resChar); // sets nextGame
10268             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10269             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10270         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10271
10272         if (nextGame <= appData.matchGames && !abortMatch) {
10273             gameMode = nextGameMode;
10274             matchGame = nextGame; // this will be overruled in tourney mode!
10275             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10276             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10277             endingGame = 0; /* [HGM] crash */
10278             return;
10279         } else {
10280             gameMode = nextGameMode;
10281             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10282                      first.tidy, second.tidy,
10283                      first.matchWins, second.matchWins,
10284                      appData.matchGames - (first.matchWins + second.matchWins));
10285             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10286             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10287             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10288                 first.twoMachinesColor = "black\n";
10289                 second.twoMachinesColor = "white\n";
10290             } else {
10291                 first.twoMachinesColor = "white\n";
10292                 second.twoMachinesColor = "black\n";
10293             }
10294         }
10295     }
10296     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10297         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10298       ExitAnalyzeMode();
10299     gameMode = nextGameMode;
10300     ModeHighlight();
10301     endingGame = 0;  /* [HGM] crash */
10302     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10303         if(matchMode == TRUE) { // match through command line: exit with or without popup
10304             if(ranking) {
10305                 ToNrEvent(forwardMostMove);
10306                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10307                 else ExitEvent(0);
10308             } else DisplayFatalError(buf, 0, 0);
10309         } else { // match through menu; just stop, with or without popup
10310             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10311             ModeHighlight();
10312             if(ranking){
10313                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10314             } else DisplayNote(buf);
10315       }
10316       if(ranking) free(ranking);
10317     }
10318 }
10319
10320 /* Assumes program was just initialized (initString sent).
10321    Leaves program in force mode. */
10322 void
10323 FeedMovesToProgram(cps, upto)
10324      ChessProgramState *cps;
10325      int upto;
10326 {
10327     int i;
10328
10329     if (appData.debugMode)
10330       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10331               startedFromSetupPosition ? "position and " : "",
10332               backwardMostMove, upto, cps->which);
10333     if(currentlyInitializedVariant != gameInfo.variant) {
10334       char buf[MSG_SIZ];
10335         // [HGM] variantswitch: make engine aware of new variant
10336         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10337                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10338         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10339         SendToProgram(buf, cps);
10340         currentlyInitializedVariant = gameInfo.variant;
10341     }
10342     SendToProgram("force\n", cps);
10343     if (startedFromSetupPosition) {
10344         SendBoard(cps, backwardMostMove);
10345     if (appData.debugMode) {
10346         fprintf(debugFP, "feedMoves\n");
10347     }
10348     }
10349     for (i = backwardMostMove; i < upto; i++) {
10350         SendMoveToProgram(i, cps);
10351     }
10352 }
10353
10354
10355 int
10356 ResurrectChessProgram()
10357 {
10358      /* The chess program may have exited.
10359         If so, restart it and feed it all the moves made so far. */
10360     static int doInit = 0;
10361
10362     if (appData.noChessProgram) return 1;
10363
10364     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10365         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10366         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10367         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10368     } else {
10369         if (first.pr != NoProc) return 1;
10370         StartChessProgram(&first);
10371     }
10372     InitChessProgram(&first, FALSE);
10373     FeedMovesToProgram(&first, currentMove);
10374
10375     if (!first.sendTime) {
10376         /* can't tell gnuchess what its clock should read,
10377            so we bow to its notion. */
10378         ResetClocks();
10379         timeRemaining[0][currentMove] = whiteTimeRemaining;
10380         timeRemaining[1][currentMove] = blackTimeRemaining;
10381     }
10382
10383     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10384                 appData.icsEngineAnalyze) && first.analysisSupport) {
10385       SendToProgram("analyze\n", &first);
10386       first.analyzing = TRUE;
10387     }
10388     return 1;
10389 }
10390
10391 /*
10392  * Button procedures
10393  */
10394 void
10395 Reset(redraw, init)
10396      int redraw, init;
10397 {
10398     int i;
10399
10400     if (appData.debugMode) {
10401         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10402                 redraw, init, gameMode);
10403     }
10404     CleanupTail(); // [HGM] vari: delete any stored variations
10405     pausing = pauseExamInvalid = FALSE;
10406     startedFromSetupPosition = blackPlaysFirst = FALSE;
10407     firstMove = TRUE;
10408     whiteFlag = blackFlag = FALSE;
10409     userOfferedDraw = FALSE;
10410     hintRequested = bookRequested = FALSE;
10411     first.maybeThinking = FALSE;
10412     second.maybeThinking = FALSE;
10413     first.bookSuspend = FALSE; // [HGM] book
10414     second.bookSuspend = FALSE;
10415     thinkOutput[0] = NULLCHAR;
10416     lastHint[0] = NULLCHAR;
10417     ClearGameInfo(&gameInfo);
10418     gameInfo.variant = StringToVariant(appData.variant);
10419     ics_user_moved = ics_clock_paused = FALSE;
10420     ics_getting_history = H_FALSE;
10421     ics_gamenum = -1;
10422     white_holding[0] = black_holding[0] = NULLCHAR;
10423     ClearProgramStats();
10424     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10425
10426     ResetFrontEnd();
10427     ClearHighlights();
10428     flipView = appData.flipView;
10429     ClearPremoveHighlights();
10430     gotPremove = FALSE;
10431     alarmSounded = FALSE;
10432
10433     GameEnds(EndOfFile, NULL, GE_PLAYER);
10434     if(appData.serverMovesName != NULL) {
10435         /* [HGM] prepare to make moves file for broadcasting */
10436         clock_t t = clock();
10437         if(serverMoves != NULL) fclose(serverMoves);
10438         serverMoves = fopen(appData.serverMovesName, "r");
10439         if(serverMoves != NULL) {
10440             fclose(serverMoves);
10441             /* delay 15 sec before overwriting, so all clients can see end */
10442             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10443         }
10444         serverMoves = fopen(appData.serverMovesName, "w");
10445     }
10446
10447     ExitAnalyzeMode();
10448     gameMode = BeginningOfGame;
10449     ModeHighlight();
10450     if(appData.icsActive) gameInfo.variant = VariantNormal;
10451     currentMove = forwardMostMove = backwardMostMove = 0;
10452     InitPosition(redraw);
10453     for (i = 0; i < MAX_MOVES; i++) {
10454         if (commentList[i] != NULL) {
10455             free(commentList[i]);
10456             commentList[i] = NULL;
10457         }
10458     }
10459     ResetClocks();
10460     timeRemaining[0][0] = whiteTimeRemaining;
10461     timeRemaining[1][0] = blackTimeRemaining;
10462
10463     if (first.pr == NULL) {
10464         StartChessProgram(&first);
10465     }
10466     if (init) {
10467             InitChessProgram(&first, startedFromSetupPosition);
10468     }
10469     DisplayTitle("");
10470     DisplayMessage("", "");
10471     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10472     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10473 }
10474
10475 void
10476 AutoPlayGameLoop()
10477 {
10478     for (;;) {
10479         if (!AutoPlayOneMove())
10480           return;
10481         if (matchMode || appData.timeDelay == 0)
10482           continue;
10483         if (appData.timeDelay < 0)
10484           return;
10485         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10486         break;
10487     }
10488 }
10489
10490
10491 int
10492 AutoPlayOneMove()
10493 {
10494     int fromX, fromY, toX, toY;
10495
10496     if (appData.debugMode) {
10497       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10498     }
10499
10500     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10501       return FALSE;
10502
10503     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10504       pvInfoList[currentMove].depth = programStats.depth;
10505       pvInfoList[currentMove].score = programStats.score;
10506       pvInfoList[currentMove].time  = 0;
10507       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10508     }
10509
10510     if (currentMove >= forwardMostMove) {
10511       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10512       gameMode = EditGame;
10513       ModeHighlight();
10514
10515       /* [AS] Clear current move marker at the end of a game */
10516       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10517
10518       return FALSE;
10519     }
10520
10521     toX = moveList[currentMove][2] - AAA;
10522     toY = moveList[currentMove][3] - ONE;
10523
10524     if (moveList[currentMove][1] == '@') {
10525         if (appData.highlightLastMove) {
10526             SetHighlights(-1, -1, toX, toY);
10527         }
10528     } else {
10529         fromX = moveList[currentMove][0] - AAA;
10530         fromY = moveList[currentMove][1] - ONE;
10531
10532         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10533
10534         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10535
10536         if (appData.highlightLastMove) {
10537             SetHighlights(fromX, fromY, toX, toY);
10538         }
10539     }
10540     DisplayMove(currentMove);
10541     SendMoveToProgram(currentMove++, &first);
10542     DisplayBothClocks();
10543     DrawPosition(FALSE, boards[currentMove]);
10544     // [HGM] PV info: always display, routine tests if empty
10545     DisplayComment(currentMove - 1, commentList[currentMove]);
10546     return TRUE;
10547 }
10548
10549
10550 int
10551 LoadGameOneMove(readAhead)
10552      ChessMove readAhead;
10553 {
10554     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10555     char promoChar = NULLCHAR;
10556     ChessMove moveType;
10557     char move[MSG_SIZ];
10558     char *p, *q;
10559
10560     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10561         gameMode != AnalyzeMode && gameMode != Training) {
10562         gameFileFP = NULL;
10563         return FALSE;
10564     }
10565
10566     yyboardindex = forwardMostMove;
10567     if (readAhead != EndOfFile) {
10568       moveType = readAhead;
10569     } else {
10570       if (gameFileFP == NULL)
10571           return FALSE;
10572       moveType = (ChessMove) Myylex();
10573     }
10574
10575     done = FALSE;
10576     switch (moveType) {
10577       case Comment:
10578         if (appData.debugMode)
10579           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10580         p = yy_text;
10581
10582         /* append the comment but don't display it */
10583         AppendComment(currentMove, p, FALSE);
10584         return TRUE;
10585
10586       case WhiteCapturesEnPassant:
10587       case BlackCapturesEnPassant:
10588       case WhitePromotion:
10589       case BlackPromotion:
10590       case WhiteNonPromotion:
10591       case BlackNonPromotion:
10592       case NormalMove:
10593       case WhiteKingSideCastle:
10594       case WhiteQueenSideCastle:
10595       case BlackKingSideCastle:
10596       case BlackQueenSideCastle:
10597       case WhiteKingSideCastleWild:
10598       case WhiteQueenSideCastleWild:
10599       case BlackKingSideCastleWild:
10600       case BlackQueenSideCastleWild:
10601       /* PUSH Fabien */
10602       case WhiteHSideCastleFR:
10603       case WhiteASideCastleFR:
10604       case BlackHSideCastleFR:
10605       case BlackASideCastleFR:
10606       /* POP Fabien */
10607         if (appData.debugMode)
10608           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10609         fromX = currentMoveString[0] - AAA;
10610         fromY = currentMoveString[1] - ONE;
10611         toX = currentMoveString[2] - AAA;
10612         toY = currentMoveString[3] - ONE;
10613         promoChar = currentMoveString[4];
10614         break;
10615
10616       case WhiteDrop:
10617       case BlackDrop:
10618         if (appData.debugMode)
10619           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10620         fromX = moveType == WhiteDrop ?
10621           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10622         (int) CharToPiece(ToLower(currentMoveString[0]));
10623         fromY = DROP_RANK;
10624         toX = currentMoveString[2] - AAA;
10625         toY = currentMoveString[3] - ONE;
10626         break;
10627
10628       case WhiteWins:
10629       case BlackWins:
10630       case GameIsDrawn:
10631       case GameUnfinished:
10632         if (appData.debugMode)
10633           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10634         p = strchr(yy_text, '{');
10635         if (p == NULL) p = strchr(yy_text, '(');
10636         if (p == NULL) {
10637             p = yy_text;
10638             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10639         } else {
10640             q = strchr(p, *p == '{' ? '}' : ')');
10641             if (q != NULL) *q = NULLCHAR;
10642             p++;
10643         }
10644         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10645         GameEnds(moveType, p, GE_FILE);
10646         done = TRUE;
10647         if (cmailMsgLoaded) {
10648             ClearHighlights();
10649             flipView = WhiteOnMove(currentMove);
10650             if (moveType == GameUnfinished) flipView = !flipView;
10651             if (appData.debugMode)
10652               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10653         }
10654         break;
10655
10656       case EndOfFile:
10657         if (appData.debugMode)
10658           fprintf(debugFP, "Parser hit end of file\n");
10659         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10660           case MT_NONE:
10661           case MT_CHECK:
10662             break;
10663           case MT_CHECKMATE:
10664           case MT_STAINMATE:
10665             if (WhiteOnMove(currentMove)) {
10666                 GameEnds(BlackWins, "Black mates", GE_FILE);
10667             } else {
10668                 GameEnds(WhiteWins, "White mates", GE_FILE);
10669             }
10670             break;
10671           case MT_STALEMATE:
10672             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10673             break;
10674         }
10675         done = TRUE;
10676         break;
10677
10678       case MoveNumberOne:
10679         if (lastLoadGameStart == GNUChessGame) {
10680             /* GNUChessGames have numbers, but they aren't move numbers */
10681             if (appData.debugMode)
10682               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10683                       yy_text, (int) moveType);
10684             return LoadGameOneMove(EndOfFile); /* tail recursion */
10685         }
10686         /* else fall thru */
10687
10688       case XBoardGame:
10689       case GNUChessGame:
10690       case PGNTag:
10691         /* Reached start of next game in file */
10692         if (appData.debugMode)
10693           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10694         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10695           case MT_NONE:
10696           case MT_CHECK:
10697             break;
10698           case MT_CHECKMATE:
10699           case MT_STAINMATE:
10700             if (WhiteOnMove(currentMove)) {
10701                 GameEnds(BlackWins, "Black mates", GE_FILE);
10702             } else {
10703                 GameEnds(WhiteWins, "White mates", GE_FILE);
10704             }
10705             break;
10706           case MT_STALEMATE:
10707             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10708             break;
10709         }
10710         done = TRUE;
10711         break;
10712
10713       case PositionDiagram:     /* should not happen; ignore */
10714       case ElapsedTime:         /* ignore */
10715       case NAG:                 /* ignore */
10716         if (appData.debugMode)
10717           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10718                   yy_text, (int) moveType);
10719         return LoadGameOneMove(EndOfFile); /* tail recursion */
10720
10721       case IllegalMove:
10722         if (appData.testLegality) {
10723             if (appData.debugMode)
10724               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10725             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10726                     (forwardMostMove / 2) + 1,
10727                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10728             DisplayError(move, 0);
10729             done = TRUE;
10730         } else {
10731             if (appData.debugMode)
10732               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10733                       yy_text, currentMoveString);
10734             fromX = currentMoveString[0] - AAA;
10735             fromY = currentMoveString[1] - ONE;
10736             toX = currentMoveString[2] - AAA;
10737             toY = currentMoveString[3] - ONE;
10738             promoChar = currentMoveString[4];
10739         }
10740         break;
10741
10742       case AmbiguousMove:
10743         if (appData.debugMode)
10744           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10745         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10746                 (forwardMostMove / 2) + 1,
10747                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10748         DisplayError(move, 0);
10749         done = TRUE;
10750         break;
10751
10752       default:
10753       case ImpossibleMove:
10754         if (appData.debugMode)
10755           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10756         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10757                 (forwardMostMove / 2) + 1,
10758                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10759         DisplayError(move, 0);
10760         done = TRUE;
10761         break;
10762     }
10763
10764     if (done) {
10765         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10766             DrawPosition(FALSE, boards[currentMove]);
10767             DisplayBothClocks();
10768             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10769               DisplayComment(currentMove - 1, commentList[currentMove]);
10770         }
10771         (void) StopLoadGameTimer();
10772         gameFileFP = NULL;
10773         cmailOldMove = forwardMostMove;
10774         return FALSE;
10775     } else {
10776         /* currentMoveString is set as a side-effect of yylex */
10777
10778         thinkOutput[0] = NULLCHAR;
10779         MakeMove(fromX, fromY, toX, toY, promoChar);
10780         currentMove = forwardMostMove;
10781         return TRUE;
10782     }
10783 }
10784
10785 /* Load the nth game from the given file */
10786 int
10787 LoadGameFromFile(filename, n, title, useList)
10788      char *filename;
10789      int n;
10790      char *title;
10791      /*Boolean*/ int useList;
10792 {
10793     FILE *f;
10794     char buf[MSG_SIZ];
10795
10796     if (strcmp(filename, "-") == 0) {
10797         f = stdin;
10798         title = "stdin";
10799     } else {
10800         f = fopen(filename, "rb");
10801         if (f == NULL) {
10802           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10803             DisplayError(buf, errno);
10804             return FALSE;
10805         }
10806     }
10807     if (fseek(f, 0, 0) == -1) {
10808         /* f is not seekable; probably a pipe */
10809         useList = FALSE;
10810     }
10811     if (useList && n == 0) {
10812         int error = GameListBuild(f);
10813         if (error) {
10814             DisplayError(_("Cannot build game list"), error);
10815         } else if (!ListEmpty(&gameList) &&
10816                    ((ListGame *) gameList.tailPred)->number > 1) {
10817             GameListPopUp(f, title);
10818             return TRUE;
10819         }
10820         GameListDestroy();
10821         n = 1;
10822     }
10823     if (n == 0) n = 1;
10824     return LoadGame(f, n, title, FALSE);
10825 }
10826
10827
10828 void
10829 MakeRegisteredMove()
10830 {
10831     int fromX, fromY, toX, toY;
10832     char promoChar;
10833     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10834         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10835           case CMAIL_MOVE:
10836           case CMAIL_DRAW:
10837             if (appData.debugMode)
10838               fprintf(debugFP, "Restoring %s for game %d\n",
10839                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10840
10841             thinkOutput[0] = NULLCHAR;
10842             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10843             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10844             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10845             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10846             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10847             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10848             MakeMove(fromX, fromY, toX, toY, promoChar);
10849             ShowMove(fromX, fromY, toX, toY);
10850
10851             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10852               case MT_NONE:
10853               case MT_CHECK:
10854                 break;
10855
10856               case MT_CHECKMATE:
10857               case MT_STAINMATE:
10858                 if (WhiteOnMove(currentMove)) {
10859                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10860                 } else {
10861                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10862                 }
10863                 break;
10864
10865               case MT_STALEMATE:
10866                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10867                 break;
10868             }
10869
10870             break;
10871
10872           case CMAIL_RESIGN:
10873             if (WhiteOnMove(currentMove)) {
10874                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10875             } else {
10876                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10877             }
10878             break;
10879
10880           case CMAIL_ACCEPT:
10881             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10882             break;
10883
10884           default:
10885             break;
10886         }
10887     }
10888
10889     return;
10890 }
10891
10892 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10893 int
10894 CmailLoadGame(f, gameNumber, title, useList)
10895      FILE *f;
10896      int gameNumber;
10897      char *title;
10898      int useList;
10899 {
10900     int retVal;
10901
10902     if (gameNumber > nCmailGames) {
10903         DisplayError(_("No more games in this message"), 0);
10904         return FALSE;
10905     }
10906     if (f == lastLoadGameFP) {
10907         int offset = gameNumber - lastLoadGameNumber;
10908         if (offset == 0) {
10909             cmailMsg[0] = NULLCHAR;
10910             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10911                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10912                 nCmailMovesRegistered--;
10913             }
10914             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10915             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10916                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10917             }
10918         } else {
10919             if (! RegisterMove()) return FALSE;
10920         }
10921     }
10922
10923     retVal = LoadGame(f, gameNumber, title, useList);
10924
10925     /* Make move registered during previous look at this game, if any */
10926     MakeRegisteredMove();
10927
10928     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10929         commentList[currentMove]
10930           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10931         DisplayComment(currentMove - 1, commentList[currentMove]);
10932     }
10933
10934     return retVal;
10935 }
10936
10937 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10938 int
10939 ReloadGame(offset)
10940      int offset;
10941 {
10942     int gameNumber = lastLoadGameNumber + offset;
10943     if (lastLoadGameFP == NULL) {
10944         DisplayError(_("No game has been loaded yet"), 0);
10945         return FALSE;
10946     }
10947     if (gameNumber <= 0) {
10948         DisplayError(_("Can't back up any further"), 0);
10949         return FALSE;
10950     }
10951     if (cmailMsgLoaded) {
10952         return CmailLoadGame(lastLoadGameFP, gameNumber,
10953                              lastLoadGameTitle, lastLoadGameUseList);
10954     } else {
10955         return LoadGame(lastLoadGameFP, gameNumber,
10956                         lastLoadGameTitle, lastLoadGameUseList);
10957     }
10958 }
10959
10960
10961
10962 /* Load the nth game from open file f */
10963 int
10964 LoadGame(f, gameNumber, title, useList)
10965      FILE *f;
10966      int gameNumber;
10967      char *title;
10968      int useList;
10969 {
10970     ChessMove cm;
10971     char buf[MSG_SIZ];
10972     int gn = gameNumber;
10973     ListGame *lg = NULL;
10974     int numPGNTags = 0;
10975     int err;
10976     GameMode oldGameMode;
10977     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10978
10979     if (appData.debugMode)
10980         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10981
10982     if (gameMode == Training )
10983         SetTrainingModeOff();
10984
10985     oldGameMode = gameMode;
10986     if (gameMode != BeginningOfGame) {
10987       Reset(FALSE, TRUE);
10988     }
10989
10990     gameFileFP = f;
10991     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10992         fclose(lastLoadGameFP);
10993     }
10994
10995     if (useList) {
10996         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10997
10998         if (lg) {
10999             fseek(f, lg->offset, 0);
11000             GameListHighlight(gameNumber);
11001             gn = 1;
11002         }
11003         else {
11004             DisplayError(_("Game number out of range"), 0);
11005             return FALSE;
11006         }
11007     } else {
11008         GameListDestroy();
11009         if (fseek(f, 0, 0) == -1) {
11010             if (f == lastLoadGameFP ?
11011                 gameNumber == lastLoadGameNumber + 1 :
11012                 gameNumber == 1) {
11013                 gn = 1;
11014             } else {
11015                 DisplayError(_("Can't seek on game file"), 0);
11016                 return FALSE;
11017             }
11018         }
11019     }
11020     lastLoadGameFP = f;
11021     lastLoadGameNumber = gameNumber;
11022     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11023     lastLoadGameUseList = useList;
11024
11025     yynewfile(f);
11026
11027     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11028       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11029                 lg->gameInfo.black);
11030             DisplayTitle(buf);
11031     } else if (*title != NULLCHAR) {
11032         if (gameNumber > 1) {
11033           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11034             DisplayTitle(buf);
11035         } else {
11036             DisplayTitle(title);
11037         }
11038     }
11039
11040     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11041         gameMode = PlayFromGameFile;
11042         ModeHighlight();
11043     }
11044
11045     currentMove = forwardMostMove = backwardMostMove = 0;
11046     CopyBoard(boards[0], initialPosition);
11047     StopClocks();
11048
11049     /*
11050      * Skip the first gn-1 games in the file.
11051      * Also skip over anything that precedes an identifiable
11052      * start of game marker, to avoid being confused by
11053      * garbage at the start of the file.  Currently
11054      * recognized start of game markers are the move number "1",
11055      * the pattern "gnuchess .* game", the pattern
11056      * "^[#;%] [^ ]* game file", and a PGN tag block.
11057      * A game that starts with one of the latter two patterns
11058      * will also have a move number 1, possibly
11059      * following a position diagram.
11060      * 5-4-02: Let's try being more lenient and allowing a game to
11061      * start with an unnumbered move.  Does that break anything?
11062      */
11063     cm = lastLoadGameStart = EndOfFile;
11064     while (gn > 0) {
11065         yyboardindex = forwardMostMove;
11066         cm = (ChessMove) Myylex();
11067         switch (cm) {
11068           case EndOfFile:
11069             if (cmailMsgLoaded) {
11070                 nCmailGames = CMAIL_MAX_GAMES - gn;
11071             } else {
11072                 Reset(TRUE, TRUE);
11073                 DisplayError(_("Game not found in file"), 0);
11074             }
11075             return FALSE;
11076
11077           case GNUChessGame:
11078           case XBoardGame:
11079             gn--;
11080             lastLoadGameStart = cm;
11081             break;
11082
11083           case MoveNumberOne:
11084             switch (lastLoadGameStart) {
11085               case GNUChessGame:
11086               case XBoardGame:
11087               case PGNTag:
11088                 break;
11089               case MoveNumberOne:
11090               case EndOfFile:
11091                 gn--;           /* count this game */
11092                 lastLoadGameStart = cm;
11093                 break;
11094               default:
11095                 /* impossible */
11096                 break;
11097             }
11098             break;
11099
11100           case PGNTag:
11101             switch (lastLoadGameStart) {
11102               case GNUChessGame:
11103               case PGNTag:
11104               case MoveNumberOne:
11105               case EndOfFile:
11106                 gn--;           /* count this game */
11107                 lastLoadGameStart = cm;
11108                 break;
11109               case XBoardGame:
11110                 lastLoadGameStart = cm; /* game counted already */
11111                 break;
11112               default:
11113                 /* impossible */
11114                 break;
11115             }
11116             if (gn > 0) {
11117                 do {
11118                     yyboardindex = forwardMostMove;
11119                     cm = (ChessMove) Myylex();
11120                 } while (cm == PGNTag || cm == Comment);
11121             }
11122             break;
11123
11124           case WhiteWins:
11125           case BlackWins:
11126           case GameIsDrawn:
11127             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11128                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11129                     != CMAIL_OLD_RESULT) {
11130                     nCmailResults ++ ;
11131                     cmailResult[  CMAIL_MAX_GAMES
11132                                 - gn - 1] = CMAIL_OLD_RESULT;
11133                 }
11134             }
11135             break;
11136
11137           case NormalMove:
11138             /* Only a NormalMove can be at the start of a game
11139              * without a position diagram. */
11140             if (lastLoadGameStart == EndOfFile ) {
11141               gn--;
11142               lastLoadGameStart = MoveNumberOne;
11143             }
11144             break;
11145
11146           default:
11147             break;
11148         }
11149     }
11150
11151     if (appData.debugMode)
11152       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11153
11154     if (cm == XBoardGame) {
11155         /* Skip any header junk before position diagram and/or move 1 */
11156         for (;;) {
11157             yyboardindex = forwardMostMove;
11158             cm = (ChessMove) Myylex();
11159
11160             if (cm == EndOfFile ||
11161                 cm == GNUChessGame || cm == XBoardGame) {
11162                 /* Empty game; pretend end-of-file and handle later */
11163                 cm = EndOfFile;
11164                 break;
11165             }
11166
11167             if (cm == MoveNumberOne || cm == PositionDiagram ||
11168                 cm == PGNTag || cm == Comment)
11169               break;
11170         }
11171     } else if (cm == GNUChessGame) {
11172         if (gameInfo.event != NULL) {
11173             free(gameInfo.event);
11174         }
11175         gameInfo.event = StrSave(yy_text);
11176     }
11177
11178     startedFromSetupPosition = FALSE;
11179     while (cm == PGNTag) {
11180         if (appData.debugMode)
11181           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11182         err = ParsePGNTag(yy_text, &gameInfo);
11183         if (!err) numPGNTags++;
11184
11185         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11186         if(gameInfo.variant != oldVariant) {
11187             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11188             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11189             InitPosition(TRUE);
11190             oldVariant = gameInfo.variant;
11191             if (appData.debugMode)
11192               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11193         }
11194
11195
11196         if (gameInfo.fen != NULL) {
11197           Board initial_position;
11198           startedFromSetupPosition = TRUE;
11199           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11200             Reset(TRUE, TRUE);
11201             DisplayError(_("Bad FEN position in file"), 0);
11202             return FALSE;
11203           }
11204           CopyBoard(boards[0], initial_position);
11205           if (blackPlaysFirst) {
11206             currentMove = forwardMostMove = backwardMostMove = 1;
11207             CopyBoard(boards[1], initial_position);
11208             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11209             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11210             timeRemaining[0][1] = whiteTimeRemaining;
11211             timeRemaining[1][1] = blackTimeRemaining;
11212             if (commentList[0] != NULL) {
11213               commentList[1] = commentList[0];
11214               commentList[0] = NULL;
11215             }
11216           } else {
11217             currentMove = forwardMostMove = backwardMostMove = 0;
11218           }
11219           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11220           {   int i;
11221               initialRulePlies = FENrulePlies;
11222               for( i=0; i< nrCastlingRights; i++ )
11223                   initialRights[i] = initial_position[CASTLING][i];
11224           }
11225           yyboardindex = forwardMostMove;
11226           free(gameInfo.fen);
11227           gameInfo.fen = NULL;
11228         }
11229
11230         yyboardindex = forwardMostMove;
11231         cm = (ChessMove) Myylex();
11232
11233         /* Handle comments interspersed among the tags */
11234         while (cm == Comment) {
11235             char *p;
11236             if (appData.debugMode)
11237               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11238             p = yy_text;
11239             AppendComment(currentMove, p, FALSE);
11240             yyboardindex = forwardMostMove;
11241             cm = (ChessMove) Myylex();
11242         }
11243     }
11244
11245     /* don't rely on existence of Event tag since if game was
11246      * pasted from clipboard the Event tag may not exist
11247      */
11248     if (numPGNTags > 0){
11249         char *tags;
11250         if (gameInfo.variant == VariantNormal) {
11251           VariantClass v = StringToVariant(gameInfo.event);
11252           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11253           if(v < VariantShogi) gameInfo.variant = v;
11254         }
11255         if (!matchMode) {
11256           if( appData.autoDisplayTags ) {
11257             tags = PGNTags(&gameInfo);
11258             TagsPopUp(tags, CmailMsg());
11259             free(tags);
11260           }
11261         }
11262     } else {
11263         /* Make something up, but don't display it now */
11264         SetGameInfo();
11265         TagsPopDown();
11266     }
11267
11268     if (cm == PositionDiagram) {
11269         int i, j;
11270         char *p;
11271         Board initial_position;
11272
11273         if (appData.debugMode)
11274           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11275
11276         if (!startedFromSetupPosition) {
11277             p = yy_text;
11278             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11279               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11280                 switch (*p) {
11281                   case '{':
11282                   case '[':
11283                   case '-':
11284                   case ' ':
11285                   case '\t':
11286                   case '\n':
11287                   case '\r':
11288                     break;
11289                   default:
11290                     initial_position[i][j++] = CharToPiece(*p);
11291                     break;
11292                 }
11293             while (*p == ' ' || *p == '\t' ||
11294                    *p == '\n' || *p == '\r') p++;
11295
11296             if (strncmp(p, "black", strlen("black"))==0)
11297               blackPlaysFirst = TRUE;
11298             else
11299               blackPlaysFirst = FALSE;
11300             startedFromSetupPosition = TRUE;
11301
11302             CopyBoard(boards[0], initial_position);
11303             if (blackPlaysFirst) {
11304                 currentMove = forwardMostMove = backwardMostMove = 1;
11305                 CopyBoard(boards[1], initial_position);
11306                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11307                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11308                 timeRemaining[0][1] = whiteTimeRemaining;
11309                 timeRemaining[1][1] = blackTimeRemaining;
11310                 if (commentList[0] != NULL) {
11311                     commentList[1] = commentList[0];
11312                     commentList[0] = NULL;
11313                 }
11314             } else {
11315                 currentMove = forwardMostMove = backwardMostMove = 0;
11316             }
11317         }
11318         yyboardindex = forwardMostMove;
11319         cm = (ChessMove) Myylex();
11320     }
11321
11322     if (first.pr == NoProc) {
11323         StartChessProgram(&first);
11324     }
11325     InitChessProgram(&first, FALSE);
11326     SendToProgram("force\n", &first);
11327     if (startedFromSetupPosition) {
11328         SendBoard(&first, forwardMostMove);
11329     if (appData.debugMode) {
11330         fprintf(debugFP, "Load Game\n");
11331     }
11332         DisplayBothClocks();
11333     }
11334
11335     /* [HGM] server: flag to write setup moves in broadcast file as one */
11336     loadFlag = appData.suppressLoadMoves;
11337
11338     while (cm == Comment) {
11339         char *p;
11340         if (appData.debugMode)
11341           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11342         p = yy_text;
11343         AppendComment(currentMove, p, FALSE);
11344         yyboardindex = forwardMostMove;
11345         cm = (ChessMove) Myylex();
11346     }
11347
11348     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11349         cm == WhiteWins || cm == BlackWins ||
11350         cm == GameIsDrawn || cm == GameUnfinished) {
11351         DisplayMessage("", _("No moves in game"));
11352         if (cmailMsgLoaded) {
11353             if (appData.debugMode)
11354               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11355             ClearHighlights();
11356             flipView = FALSE;
11357         }
11358         DrawPosition(FALSE, boards[currentMove]);
11359         DisplayBothClocks();
11360         gameMode = EditGame;
11361         ModeHighlight();
11362         gameFileFP = NULL;
11363         cmailOldMove = 0;
11364         return TRUE;
11365     }
11366
11367     // [HGM] PV info: routine tests if comment empty
11368     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11369         DisplayComment(currentMove - 1, commentList[currentMove]);
11370     }
11371     if (!matchMode && appData.timeDelay != 0)
11372       DrawPosition(FALSE, boards[currentMove]);
11373
11374     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11375       programStats.ok_to_send = 1;
11376     }
11377
11378     /* if the first token after the PGN tags is a move
11379      * and not move number 1, retrieve it from the parser
11380      */
11381     if (cm != MoveNumberOne)
11382         LoadGameOneMove(cm);
11383
11384     /* load the remaining moves from the file */
11385     while (LoadGameOneMove(EndOfFile)) {
11386       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11387       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11388     }
11389
11390     /* rewind to the start of the game */
11391     currentMove = backwardMostMove;
11392
11393     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11394
11395     if (oldGameMode == AnalyzeFile ||
11396         oldGameMode == AnalyzeMode) {
11397       AnalyzeFileEvent();
11398     }
11399
11400     if (matchMode || appData.timeDelay == 0) {
11401       ToEndEvent();
11402       gameMode = EditGame;
11403       ModeHighlight();
11404     } else if (appData.timeDelay > 0) {
11405       AutoPlayGameLoop();
11406     }
11407
11408     if (appData.debugMode)
11409         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11410
11411     loadFlag = 0; /* [HGM] true game starts */
11412     return TRUE;
11413 }
11414
11415 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11416 int
11417 ReloadPosition(offset)
11418      int offset;
11419 {
11420     int positionNumber = lastLoadPositionNumber + offset;
11421     if (lastLoadPositionFP == NULL) {
11422         DisplayError(_("No position has been loaded yet"), 0);
11423         return FALSE;
11424     }
11425     if (positionNumber <= 0) {
11426         DisplayError(_("Can't back up any further"), 0);
11427         return FALSE;
11428     }
11429     return LoadPosition(lastLoadPositionFP, positionNumber,
11430                         lastLoadPositionTitle);
11431 }
11432
11433 /* Load the nth position from the given file */
11434 int
11435 LoadPositionFromFile(filename, n, title)
11436      char *filename;
11437      int n;
11438      char *title;
11439 {
11440     FILE *f;
11441     char buf[MSG_SIZ];
11442
11443     if (strcmp(filename, "-") == 0) {
11444         return LoadPosition(stdin, n, "stdin");
11445     } else {
11446         f = fopen(filename, "rb");
11447         if (f == NULL) {
11448             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11449             DisplayError(buf, errno);
11450             return FALSE;
11451         } else {
11452             return LoadPosition(f, n, title);
11453         }
11454     }
11455 }
11456
11457 /* Load the nth position from the given open file, and close it */
11458 int
11459 LoadPosition(f, positionNumber, title)
11460      FILE *f;
11461      int positionNumber;
11462      char *title;
11463 {
11464     char *p, line[MSG_SIZ];
11465     Board initial_position;
11466     int i, j, fenMode, pn;
11467
11468     if (gameMode == Training )
11469         SetTrainingModeOff();
11470
11471     if (gameMode != BeginningOfGame) {
11472         Reset(FALSE, TRUE);
11473     }
11474     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11475         fclose(lastLoadPositionFP);
11476     }
11477     if (positionNumber == 0) positionNumber = 1;
11478     lastLoadPositionFP = f;
11479     lastLoadPositionNumber = positionNumber;
11480     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11481     if (first.pr == NoProc) {
11482       StartChessProgram(&first);
11483       InitChessProgram(&first, FALSE);
11484     }
11485     pn = positionNumber;
11486     if (positionNumber < 0) {
11487         /* Negative position number means to seek to that byte offset */
11488         if (fseek(f, -positionNumber, 0) == -1) {
11489             DisplayError(_("Can't seek on position file"), 0);
11490             return FALSE;
11491         };
11492         pn = 1;
11493     } else {
11494         if (fseek(f, 0, 0) == -1) {
11495             if (f == lastLoadPositionFP ?
11496                 positionNumber == lastLoadPositionNumber + 1 :
11497                 positionNumber == 1) {
11498                 pn = 1;
11499             } else {
11500                 DisplayError(_("Can't seek on position file"), 0);
11501                 return FALSE;
11502             }
11503         }
11504     }
11505     /* See if this file is FEN or old-style xboard */
11506     if (fgets(line, MSG_SIZ, f) == NULL) {
11507         DisplayError(_("Position not found in file"), 0);
11508         return FALSE;
11509     }
11510     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11511     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11512
11513     if (pn >= 2) {
11514         if (fenMode || line[0] == '#') pn--;
11515         while (pn > 0) {
11516             /* skip positions before number pn */
11517             if (fgets(line, MSG_SIZ, f) == NULL) {
11518                 Reset(TRUE, TRUE);
11519                 DisplayError(_("Position not found in file"), 0);
11520                 return FALSE;
11521             }
11522             if (fenMode || line[0] == '#') pn--;
11523         }
11524     }
11525
11526     if (fenMode) {
11527         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11528             DisplayError(_("Bad FEN position in file"), 0);
11529             return FALSE;
11530         }
11531     } else {
11532         (void) fgets(line, MSG_SIZ, f);
11533         (void) fgets(line, MSG_SIZ, f);
11534
11535         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11536             (void) fgets(line, MSG_SIZ, f);
11537             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11538                 if (*p == ' ')
11539                   continue;
11540                 initial_position[i][j++] = CharToPiece(*p);
11541             }
11542         }
11543
11544         blackPlaysFirst = FALSE;
11545         if (!feof(f)) {
11546             (void) fgets(line, MSG_SIZ, f);
11547             if (strncmp(line, "black", strlen("black"))==0)
11548               blackPlaysFirst = TRUE;
11549         }
11550     }
11551     startedFromSetupPosition = TRUE;
11552
11553     SendToProgram("force\n", &first);
11554     CopyBoard(boards[0], initial_position);
11555     if (blackPlaysFirst) {
11556         currentMove = forwardMostMove = backwardMostMove = 1;
11557         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11558         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11559         CopyBoard(boards[1], initial_position);
11560         DisplayMessage("", _("Black to play"));
11561     } else {
11562         currentMove = forwardMostMove = backwardMostMove = 0;
11563         DisplayMessage("", _("White to play"));
11564     }
11565     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11566     SendBoard(&first, forwardMostMove);
11567     if (appData.debugMode) {
11568 int i, j;
11569   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11570   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11571         fprintf(debugFP, "Load Position\n");
11572     }
11573
11574     if (positionNumber > 1) {
11575       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11576         DisplayTitle(line);
11577     } else {
11578         DisplayTitle(title);
11579     }
11580     gameMode = EditGame;
11581     ModeHighlight();
11582     ResetClocks();
11583     timeRemaining[0][1] = whiteTimeRemaining;
11584     timeRemaining[1][1] = blackTimeRemaining;
11585     DrawPosition(FALSE, boards[currentMove]);
11586
11587     return TRUE;
11588 }
11589
11590
11591 void
11592 CopyPlayerNameIntoFileName(dest, src)
11593      char **dest, *src;
11594 {
11595     while (*src != NULLCHAR && *src != ',') {
11596         if (*src == ' ') {
11597             *(*dest)++ = '_';
11598             src++;
11599         } else {
11600             *(*dest)++ = *src++;
11601         }
11602     }
11603 }
11604
11605 char *DefaultFileName(ext)
11606      char *ext;
11607 {
11608     static char def[MSG_SIZ];
11609     char *p;
11610
11611     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11612         p = def;
11613         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11614         *p++ = '-';
11615         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11616         *p++ = '.';
11617         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11618     } else {
11619         def[0] = NULLCHAR;
11620     }
11621     return def;
11622 }
11623
11624 /* Save the current game to the given file */
11625 int
11626 SaveGameToFile(filename, append)
11627      char *filename;
11628      int append;
11629 {
11630     FILE *f;
11631     char buf[MSG_SIZ];
11632     int result;
11633
11634     if (strcmp(filename, "-") == 0) {
11635         return SaveGame(stdout, 0, NULL);
11636     } else {
11637         f = fopen(filename, append ? "a" : "w");
11638         if (f == NULL) {
11639             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11640             DisplayError(buf, errno);
11641             return FALSE;
11642         } else {
11643             safeStrCpy(buf, lastMsg, MSG_SIZ);
11644             DisplayMessage(_("Waiting for access to save file"), "");
11645             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11646             DisplayMessage(_("Saving game"), "");
11647             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11648             result = SaveGame(f, 0, NULL);
11649             DisplayMessage(buf, "");
11650             return result;
11651         }
11652     }
11653 }
11654
11655 char *
11656 SavePart(str)
11657      char *str;
11658 {
11659     static char buf[MSG_SIZ];
11660     char *p;
11661
11662     p = strchr(str, ' ');
11663     if (p == NULL) return str;
11664     strncpy(buf, str, p - str);
11665     buf[p - str] = NULLCHAR;
11666     return buf;
11667 }
11668
11669 #define PGN_MAX_LINE 75
11670
11671 #define PGN_SIDE_WHITE  0
11672 #define PGN_SIDE_BLACK  1
11673
11674 /* [AS] */
11675 static int FindFirstMoveOutOfBook( int side )
11676 {
11677     int result = -1;
11678
11679     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11680         int index = backwardMostMove;
11681         int has_book_hit = 0;
11682
11683         if( (index % 2) != side ) {
11684             index++;
11685         }
11686
11687         while( index < forwardMostMove ) {
11688             /* Check to see if engine is in book */
11689             int depth = pvInfoList[index].depth;
11690             int score = pvInfoList[index].score;
11691             int in_book = 0;
11692
11693             if( depth <= 2 ) {
11694                 in_book = 1;
11695             }
11696             else if( score == 0 && depth == 63 ) {
11697                 in_book = 1; /* Zappa */
11698             }
11699             else if( score == 2 && depth == 99 ) {
11700                 in_book = 1; /* Abrok */
11701             }
11702
11703             has_book_hit += in_book;
11704
11705             if( ! in_book ) {
11706                 result = index;
11707
11708                 break;
11709             }
11710
11711             index += 2;
11712         }
11713     }
11714
11715     return result;
11716 }
11717
11718 /* [AS] */
11719 void GetOutOfBookInfo( char * buf )
11720 {
11721     int oob[2];
11722     int i;
11723     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11724
11725     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11726     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11727
11728     *buf = '\0';
11729
11730     if( oob[0] >= 0 || oob[1] >= 0 ) {
11731         for( i=0; i<2; i++ ) {
11732             int idx = oob[i];
11733
11734             if( idx >= 0 ) {
11735                 if( i > 0 && oob[0] >= 0 ) {
11736                     strcat( buf, "   " );
11737                 }
11738
11739                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11740                 sprintf( buf+strlen(buf), "%s%.2f",
11741                     pvInfoList[idx].score >= 0 ? "+" : "",
11742                     pvInfoList[idx].score / 100.0 );
11743             }
11744         }
11745     }
11746 }
11747
11748 /* Save game in PGN style and close the file */
11749 int
11750 SaveGamePGN(f)
11751      FILE *f;
11752 {
11753     int i, offset, linelen, newblock;
11754     time_t tm;
11755 //    char *movetext;
11756     char numtext[32];
11757     int movelen, numlen, blank;
11758     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11759
11760     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11761
11762     tm = time((time_t *) NULL);
11763
11764     PrintPGNTags(f, &gameInfo);
11765
11766     if (backwardMostMove > 0 || startedFromSetupPosition) {
11767         char *fen = PositionToFEN(backwardMostMove, NULL);
11768         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11769         fprintf(f, "\n{--------------\n");
11770         PrintPosition(f, backwardMostMove);
11771         fprintf(f, "--------------}\n");
11772         free(fen);
11773     }
11774     else {
11775         /* [AS] Out of book annotation */
11776         if( appData.saveOutOfBookInfo ) {
11777             char buf[64];
11778
11779             GetOutOfBookInfo( buf );
11780
11781             if( buf[0] != '\0' ) {
11782                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11783             }
11784         }
11785
11786         fprintf(f, "\n");
11787     }
11788
11789     i = backwardMostMove;
11790     linelen = 0;
11791     newblock = TRUE;
11792
11793     while (i < forwardMostMove) {
11794         /* Print comments preceding this move */
11795         if (commentList[i] != NULL) {
11796             if (linelen > 0) fprintf(f, "\n");
11797             fprintf(f, "%s", commentList[i]);
11798             linelen = 0;
11799             newblock = TRUE;
11800         }
11801
11802         /* Format move number */
11803         if ((i % 2) == 0)
11804           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11805         else
11806           if (newblock)
11807             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11808           else
11809             numtext[0] = NULLCHAR;
11810
11811         numlen = strlen(numtext);
11812         newblock = FALSE;
11813
11814         /* Print move number */
11815         blank = linelen > 0 && numlen > 0;
11816         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11817             fprintf(f, "\n");
11818             linelen = 0;
11819             blank = 0;
11820         }
11821         if (blank) {
11822             fprintf(f, " ");
11823             linelen++;
11824         }
11825         fprintf(f, "%s", numtext);
11826         linelen += numlen;
11827
11828         /* Get move */
11829         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11830         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11831
11832         /* Print move */
11833         blank = linelen > 0 && movelen > 0;
11834         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11835             fprintf(f, "\n");
11836             linelen = 0;
11837             blank = 0;
11838         }
11839         if (blank) {
11840             fprintf(f, " ");
11841             linelen++;
11842         }
11843         fprintf(f, "%s", move_buffer);
11844         linelen += movelen;
11845
11846         /* [AS] Add PV info if present */
11847         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11848             /* [HGM] add time */
11849             char buf[MSG_SIZ]; int seconds;
11850
11851             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11852
11853             if( seconds <= 0)
11854               buf[0] = 0;
11855             else
11856               if( seconds < 30 )
11857                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11858               else
11859                 {
11860                   seconds = (seconds + 4)/10; // round to full seconds
11861                   if( seconds < 60 )
11862                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11863                   else
11864                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11865                 }
11866
11867             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11868                       pvInfoList[i].score >= 0 ? "+" : "",
11869                       pvInfoList[i].score / 100.0,
11870                       pvInfoList[i].depth,
11871                       buf );
11872
11873             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11874
11875             /* Print score/depth */
11876             blank = linelen > 0 && movelen > 0;
11877             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11878                 fprintf(f, "\n");
11879                 linelen = 0;
11880                 blank = 0;
11881             }
11882             if (blank) {
11883                 fprintf(f, " ");
11884                 linelen++;
11885             }
11886             fprintf(f, "%s", move_buffer);
11887             linelen += movelen;
11888         }
11889
11890         i++;
11891     }
11892
11893     /* Start a new line */
11894     if (linelen > 0) fprintf(f, "\n");
11895
11896     /* Print comments after last move */
11897     if (commentList[i] != NULL) {
11898         fprintf(f, "%s\n", commentList[i]);
11899     }
11900
11901     /* Print result */
11902     if (gameInfo.resultDetails != NULL &&
11903         gameInfo.resultDetails[0] != NULLCHAR) {
11904         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11905                 PGNResult(gameInfo.result));
11906     } else {
11907         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11908     }
11909
11910     fclose(f);
11911     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11912     return TRUE;
11913 }
11914
11915 /* Save game in old style and close the file */
11916 int
11917 SaveGameOldStyle(f)
11918      FILE *f;
11919 {
11920     int i, offset;
11921     time_t tm;
11922
11923     tm = time((time_t *) NULL);
11924
11925     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11926     PrintOpponents(f);
11927
11928     if (backwardMostMove > 0 || startedFromSetupPosition) {
11929         fprintf(f, "\n[--------------\n");
11930         PrintPosition(f, backwardMostMove);
11931         fprintf(f, "--------------]\n");
11932     } else {
11933         fprintf(f, "\n");
11934     }
11935
11936     i = backwardMostMove;
11937     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11938
11939     while (i < forwardMostMove) {
11940         if (commentList[i] != NULL) {
11941             fprintf(f, "[%s]\n", commentList[i]);
11942         }
11943
11944         if ((i % 2) == 1) {
11945             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11946             i++;
11947         } else {
11948             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11949             i++;
11950             if (commentList[i] != NULL) {
11951                 fprintf(f, "\n");
11952                 continue;
11953             }
11954             if (i >= forwardMostMove) {
11955                 fprintf(f, "\n");
11956                 break;
11957             }
11958             fprintf(f, "%s\n", parseList[i]);
11959             i++;
11960         }
11961     }
11962
11963     if (commentList[i] != NULL) {
11964         fprintf(f, "[%s]\n", commentList[i]);
11965     }
11966
11967     /* This isn't really the old style, but it's close enough */
11968     if (gameInfo.resultDetails != NULL &&
11969         gameInfo.resultDetails[0] != NULLCHAR) {
11970         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11971                 gameInfo.resultDetails);
11972     } else {
11973         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11974     }
11975
11976     fclose(f);
11977     return TRUE;
11978 }
11979
11980 /* Save the current game to open file f and close the file */
11981 int
11982 SaveGame(f, dummy, dummy2)
11983      FILE *f;
11984      int dummy;
11985      char *dummy2;
11986 {
11987     if (gameMode == EditPosition) EditPositionDone(TRUE);
11988     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11989     if (appData.oldSaveStyle)
11990       return SaveGameOldStyle(f);
11991     else
11992       return SaveGamePGN(f);
11993 }
11994
11995 /* Save the current position to the given file */
11996 int
11997 SavePositionToFile(filename)
11998      char *filename;
11999 {
12000     FILE *f;
12001     char buf[MSG_SIZ];
12002
12003     if (strcmp(filename, "-") == 0) {
12004         return SavePosition(stdout, 0, NULL);
12005     } else {
12006         f = fopen(filename, "a");
12007         if (f == NULL) {
12008             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12009             DisplayError(buf, errno);
12010             return FALSE;
12011         } else {
12012             safeStrCpy(buf, lastMsg, MSG_SIZ);
12013             DisplayMessage(_("Waiting for access to save file"), "");
12014             flock(fileno(f), LOCK_EX); // [HGM] lock
12015             DisplayMessage(_("Saving position"), "");
12016             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12017             SavePosition(f, 0, NULL);
12018             DisplayMessage(buf, "");
12019             return TRUE;
12020         }
12021     }
12022 }
12023
12024 /* Save the current position to the given open file and close the file */
12025 int
12026 SavePosition(f, dummy, dummy2)
12027      FILE *f;
12028      int dummy;
12029      char *dummy2;
12030 {
12031     time_t tm;
12032     char *fen;
12033
12034     if (gameMode == EditPosition) EditPositionDone(TRUE);
12035     if (appData.oldSaveStyle) {
12036         tm = time((time_t *) NULL);
12037
12038         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12039         PrintOpponents(f);
12040         fprintf(f, "[--------------\n");
12041         PrintPosition(f, currentMove);
12042         fprintf(f, "--------------]\n");
12043     } else {
12044         fen = PositionToFEN(currentMove, NULL);
12045         fprintf(f, "%s\n", fen);
12046         free(fen);
12047     }
12048     fclose(f);
12049     return TRUE;
12050 }
12051
12052 void
12053 ReloadCmailMsgEvent(unregister)
12054      int unregister;
12055 {
12056 #if !WIN32
12057     static char *inFilename = NULL;
12058     static char *outFilename;
12059     int i;
12060     struct stat inbuf, outbuf;
12061     int status;
12062
12063     /* Any registered moves are unregistered if unregister is set, */
12064     /* i.e. invoked by the signal handler */
12065     if (unregister) {
12066         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12067             cmailMoveRegistered[i] = FALSE;
12068             if (cmailCommentList[i] != NULL) {
12069                 free(cmailCommentList[i]);
12070                 cmailCommentList[i] = NULL;
12071             }
12072         }
12073         nCmailMovesRegistered = 0;
12074     }
12075
12076     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12077         cmailResult[i] = CMAIL_NOT_RESULT;
12078     }
12079     nCmailResults = 0;
12080
12081     if (inFilename == NULL) {
12082         /* Because the filenames are static they only get malloced once  */
12083         /* and they never get freed                                      */
12084         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12085         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12086
12087         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12088         sprintf(outFilename, "%s.out", appData.cmailGameName);
12089     }
12090
12091     status = stat(outFilename, &outbuf);
12092     if (status < 0) {
12093         cmailMailedMove = FALSE;
12094     } else {
12095         status = stat(inFilename, &inbuf);
12096         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12097     }
12098
12099     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12100        counts the games, notes how each one terminated, etc.
12101
12102        It would be nice to remove this kludge and instead gather all
12103        the information while building the game list.  (And to keep it
12104        in the game list nodes instead of having a bunch of fixed-size
12105        parallel arrays.)  Note this will require getting each game's
12106        termination from the PGN tags, as the game list builder does
12107        not process the game moves.  --mann
12108        */
12109     cmailMsgLoaded = TRUE;
12110     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12111
12112     /* Load first game in the file or popup game menu */
12113     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12114
12115 #endif /* !WIN32 */
12116     return;
12117 }
12118
12119 int
12120 RegisterMove()
12121 {
12122     FILE *f;
12123     char string[MSG_SIZ];
12124
12125     if (   cmailMailedMove
12126         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12127         return TRUE;            /* Allow free viewing  */
12128     }
12129
12130     /* Unregister move to ensure that we don't leave RegisterMove        */
12131     /* with the move registered when the conditions for registering no   */
12132     /* longer hold                                                       */
12133     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12134         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12135         nCmailMovesRegistered --;
12136
12137         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12138           {
12139               free(cmailCommentList[lastLoadGameNumber - 1]);
12140               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12141           }
12142     }
12143
12144     if (cmailOldMove == -1) {
12145         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12146         return FALSE;
12147     }
12148
12149     if (currentMove > cmailOldMove + 1) {
12150         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12151         return FALSE;
12152     }
12153
12154     if (currentMove < cmailOldMove) {
12155         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12156         return FALSE;
12157     }
12158
12159     if (forwardMostMove > currentMove) {
12160         /* Silently truncate extra moves */
12161         TruncateGame();
12162     }
12163
12164     if (   (currentMove == cmailOldMove + 1)
12165         || (   (currentMove == cmailOldMove)
12166             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12167                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12168         if (gameInfo.result != GameUnfinished) {
12169             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12170         }
12171
12172         if (commentList[currentMove] != NULL) {
12173             cmailCommentList[lastLoadGameNumber - 1]
12174               = StrSave(commentList[currentMove]);
12175         }
12176         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12177
12178         if (appData.debugMode)
12179           fprintf(debugFP, "Saving %s for game %d\n",
12180                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12181
12182         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12183
12184         f = fopen(string, "w");
12185         if (appData.oldSaveStyle) {
12186             SaveGameOldStyle(f); /* also closes the file */
12187
12188             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12189             f = fopen(string, "w");
12190             SavePosition(f, 0, NULL); /* also closes the file */
12191         } else {
12192             fprintf(f, "{--------------\n");
12193             PrintPosition(f, currentMove);
12194             fprintf(f, "--------------}\n\n");
12195
12196             SaveGame(f, 0, NULL); /* also closes the file*/
12197         }
12198
12199         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12200         nCmailMovesRegistered ++;
12201     } else if (nCmailGames == 1) {
12202         DisplayError(_("You have not made a move yet"), 0);
12203         return FALSE;
12204     }
12205
12206     return TRUE;
12207 }
12208
12209 void
12210 MailMoveEvent()
12211 {
12212 #if !WIN32
12213     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12214     FILE *commandOutput;
12215     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12216     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12217     int nBuffers;
12218     int i;
12219     int archived;
12220     char *arcDir;
12221
12222     if (! cmailMsgLoaded) {
12223         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12224         return;
12225     }
12226
12227     if (nCmailGames == nCmailResults) {
12228         DisplayError(_("No unfinished games"), 0);
12229         return;
12230     }
12231
12232 #if CMAIL_PROHIBIT_REMAIL
12233     if (cmailMailedMove) {
12234       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);
12235         DisplayError(msg, 0);
12236         return;
12237     }
12238 #endif
12239
12240     if (! (cmailMailedMove || RegisterMove())) return;
12241
12242     if (   cmailMailedMove
12243         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12244       snprintf(string, MSG_SIZ, partCommandString,
12245                appData.debugMode ? " -v" : "", appData.cmailGameName);
12246         commandOutput = popen(string, "r");
12247
12248         if (commandOutput == NULL) {
12249             DisplayError(_("Failed to invoke cmail"), 0);
12250         } else {
12251             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12252                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12253             }
12254             if (nBuffers > 1) {
12255                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12256                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12257                 nBytes = MSG_SIZ - 1;
12258             } else {
12259                 (void) memcpy(msg, buffer, nBytes);
12260             }
12261             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12262
12263             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12264                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12265
12266                 archived = TRUE;
12267                 for (i = 0; i < nCmailGames; i ++) {
12268                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12269                         archived = FALSE;
12270                     }
12271                 }
12272                 if (   archived
12273                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12274                         != NULL)) {
12275                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12276                            arcDir,
12277                            appData.cmailGameName,
12278                            gameInfo.date);
12279                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12280                     cmailMsgLoaded = FALSE;
12281                 }
12282             }
12283
12284             DisplayInformation(msg);
12285             pclose(commandOutput);
12286         }
12287     } else {
12288         if ((*cmailMsg) != '\0') {
12289             DisplayInformation(cmailMsg);
12290         }
12291     }
12292
12293     return;
12294 #endif /* !WIN32 */
12295 }
12296
12297 char *
12298 CmailMsg()
12299 {
12300 #if WIN32
12301     return NULL;
12302 #else
12303     int  prependComma = 0;
12304     char number[5];
12305     char string[MSG_SIZ];       /* Space for game-list */
12306     int  i;
12307
12308     if (!cmailMsgLoaded) return "";
12309
12310     if (cmailMailedMove) {
12311       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12312     } else {
12313         /* Create a list of games left */
12314       snprintf(string, MSG_SIZ, "[");
12315         for (i = 0; i < nCmailGames; i ++) {
12316             if (! (   cmailMoveRegistered[i]
12317                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12318                 if (prependComma) {
12319                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12320                 } else {
12321                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12322                     prependComma = 1;
12323                 }
12324
12325                 strcat(string, number);
12326             }
12327         }
12328         strcat(string, "]");
12329
12330         if (nCmailMovesRegistered + nCmailResults == 0) {
12331             switch (nCmailGames) {
12332               case 1:
12333                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12334                 break;
12335
12336               case 2:
12337                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12338                 break;
12339
12340               default:
12341                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12342                          nCmailGames);
12343                 break;
12344             }
12345         } else {
12346             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12347               case 1:
12348                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12349                          string);
12350                 break;
12351
12352               case 0:
12353                 if (nCmailResults == nCmailGames) {
12354                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12355                 } else {
12356                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12357                 }
12358                 break;
12359
12360               default:
12361                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12362                          string);
12363             }
12364         }
12365     }
12366     return cmailMsg;
12367 #endif /* WIN32 */
12368 }
12369
12370 void
12371 ResetGameEvent()
12372 {
12373     if (gameMode == Training)
12374       SetTrainingModeOff();
12375
12376     Reset(TRUE, TRUE);
12377     cmailMsgLoaded = FALSE;
12378     if (appData.icsActive) {
12379       SendToICS(ics_prefix);
12380       SendToICS("refresh\n");
12381     }
12382 }
12383
12384 void
12385 ExitEvent(status)
12386      int status;
12387 {
12388     exiting++;
12389     if (exiting > 2) {
12390       /* Give up on clean exit */
12391       exit(status);
12392     }
12393     if (exiting > 1) {
12394       /* Keep trying for clean exit */
12395       return;
12396     }
12397
12398     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12399
12400     if (telnetISR != NULL) {
12401       RemoveInputSource(telnetISR);
12402     }
12403     if (icsPR != NoProc) {
12404       DestroyChildProcess(icsPR, TRUE);
12405     }
12406
12407     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12408     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12409
12410     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12411     /* make sure this other one finishes before killing it!                  */
12412     if(endingGame) { int count = 0;
12413         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12414         while(endingGame && count++ < 10) DoSleep(1);
12415         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12416     }
12417
12418     /* Kill off chess programs */
12419     if (first.pr != NoProc) {
12420         ExitAnalyzeMode();
12421
12422         DoSleep( appData.delayBeforeQuit );
12423         SendToProgram("quit\n", &first);
12424         DoSleep( appData.delayAfterQuit );
12425         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12426     }
12427     if (second.pr != NoProc) {
12428         DoSleep( appData.delayBeforeQuit );
12429         SendToProgram("quit\n", &second);
12430         DoSleep( appData.delayAfterQuit );
12431         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12432     }
12433     if (first.isr != NULL) {
12434         RemoveInputSource(first.isr);
12435     }
12436     if (second.isr != NULL) {
12437         RemoveInputSource(second.isr);
12438     }
12439
12440     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12441     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12442
12443     ShutDownFrontEnd();
12444     exit(status);
12445 }
12446
12447 void
12448 PauseEvent()
12449 {
12450     if (appData.debugMode)
12451         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12452     if (pausing) {
12453         pausing = FALSE;
12454         ModeHighlight();
12455         if (gameMode == MachinePlaysWhite ||
12456             gameMode == MachinePlaysBlack) {
12457             StartClocks();
12458         } else {
12459             DisplayBothClocks();
12460         }
12461         if (gameMode == PlayFromGameFile) {
12462             if (appData.timeDelay >= 0)
12463                 AutoPlayGameLoop();
12464         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12465             Reset(FALSE, TRUE);
12466             SendToICS(ics_prefix);
12467             SendToICS("refresh\n");
12468         } else if (currentMove < forwardMostMove) {
12469             ForwardInner(forwardMostMove);
12470         }
12471         pauseExamInvalid = FALSE;
12472     } else {
12473         switch (gameMode) {
12474           default:
12475             return;
12476           case IcsExamining:
12477             pauseExamForwardMostMove = forwardMostMove;
12478             pauseExamInvalid = FALSE;
12479             /* fall through */
12480           case IcsObserving:
12481           case IcsPlayingWhite:
12482           case IcsPlayingBlack:
12483             pausing = TRUE;
12484             ModeHighlight();
12485             return;
12486           case PlayFromGameFile:
12487             (void) StopLoadGameTimer();
12488             pausing = TRUE;
12489             ModeHighlight();
12490             break;
12491           case BeginningOfGame:
12492             if (appData.icsActive) return;
12493             /* else fall through */
12494           case MachinePlaysWhite:
12495           case MachinePlaysBlack:
12496           case TwoMachinesPlay:
12497             if (forwardMostMove == 0)
12498               return;           /* don't pause if no one has moved */
12499             if ((gameMode == MachinePlaysWhite &&
12500                  !WhiteOnMove(forwardMostMove)) ||
12501                 (gameMode == MachinePlaysBlack &&
12502                  WhiteOnMove(forwardMostMove))) {
12503                 StopClocks();
12504             }
12505             pausing = TRUE;
12506             ModeHighlight();
12507             break;
12508         }
12509     }
12510 }
12511
12512 void
12513 EditCommentEvent()
12514 {
12515     char title[MSG_SIZ];
12516
12517     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12518       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12519     } else {
12520       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12521                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12522                parseList[currentMove - 1]);
12523     }
12524
12525     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12526 }
12527
12528
12529 void
12530 EditTagsEvent()
12531 {
12532     char *tags = PGNTags(&gameInfo);
12533     bookUp = FALSE;
12534     EditTagsPopUp(tags, NULL);
12535     free(tags);
12536 }
12537
12538 void
12539 AnalyzeModeEvent()
12540 {
12541     if (appData.noChessProgram || gameMode == AnalyzeMode)
12542       return;
12543
12544     if (gameMode != AnalyzeFile) {
12545         if (!appData.icsEngineAnalyze) {
12546                EditGameEvent();
12547                if (gameMode != EditGame) return;
12548         }
12549         ResurrectChessProgram();
12550         SendToProgram("analyze\n", &first);
12551         first.analyzing = TRUE;
12552         /*first.maybeThinking = TRUE;*/
12553         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12554         EngineOutputPopUp();
12555     }
12556     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12557     pausing = FALSE;
12558     ModeHighlight();
12559     SetGameInfo();
12560
12561     StartAnalysisClock();
12562     GetTimeMark(&lastNodeCountTime);
12563     lastNodeCount = 0;
12564 }
12565
12566 void
12567 AnalyzeFileEvent()
12568 {
12569     if (appData.noChessProgram || gameMode == AnalyzeFile)
12570       return;
12571
12572     if (gameMode != AnalyzeMode) {
12573         EditGameEvent();
12574         if (gameMode != EditGame) return;
12575         ResurrectChessProgram();
12576         SendToProgram("analyze\n", &first);
12577         first.analyzing = TRUE;
12578         /*first.maybeThinking = TRUE;*/
12579         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12580         EngineOutputPopUp();
12581     }
12582     gameMode = AnalyzeFile;
12583     pausing = FALSE;
12584     ModeHighlight();
12585     SetGameInfo();
12586
12587     StartAnalysisClock();
12588     GetTimeMark(&lastNodeCountTime);
12589     lastNodeCount = 0;
12590 }
12591
12592 void
12593 MachineWhiteEvent()
12594 {
12595     char buf[MSG_SIZ];
12596     char *bookHit = NULL;
12597
12598     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12599       return;
12600
12601
12602     if (gameMode == PlayFromGameFile ||
12603         gameMode == TwoMachinesPlay  ||
12604         gameMode == Training         ||
12605         gameMode == AnalyzeMode      ||
12606         gameMode == EndOfGame)
12607         EditGameEvent();
12608
12609     if (gameMode == EditPosition)
12610         EditPositionDone(TRUE);
12611
12612     if (!WhiteOnMove(currentMove)) {
12613         DisplayError(_("It is not White's turn"), 0);
12614         return;
12615     }
12616
12617     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12618       ExitAnalyzeMode();
12619
12620     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12621         gameMode == AnalyzeFile)
12622         TruncateGame();
12623
12624     ResurrectChessProgram();    /* in case it isn't running */
12625     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12626         gameMode = MachinePlaysWhite;
12627         ResetClocks();
12628     } else
12629     gameMode = MachinePlaysWhite;
12630     pausing = FALSE;
12631     ModeHighlight();
12632     SetGameInfo();
12633     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12634     DisplayTitle(buf);
12635     if (first.sendName) {
12636       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12637       SendToProgram(buf, &first);
12638     }
12639     if (first.sendTime) {
12640       if (first.useColors) {
12641         SendToProgram("black\n", &first); /*gnu kludge*/
12642       }
12643       SendTimeRemaining(&first, TRUE);
12644     }
12645     if (first.useColors) {
12646       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12647     }
12648     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12649     SetMachineThinkingEnables();
12650     first.maybeThinking = TRUE;
12651     StartClocks();
12652     firstMove = FALSE;
12653
12654     if (appData.autoFlipView && !flipView) {
12655       flipView = !flipView;
12656       DrawPosition(FALSE, NULL);
12657       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12658     }
12659
12660     if(bookHit) { // [HGM] book: simulate book reply
12661         static char bookMove[MSG_SIZ]; // a bit generous?
12662
12663         programStats.nodes = programStats.depth = programStats.time =
12664         programStats.score = programStats.got_only_move = 0;
12665         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12666
12667         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12668         strcat(bookMove, bookHit);
12669         HandleMachineMove(bookMove, &first);
12670     }
12671 }
12672
12673 void
12674 MachineBlackEvent()
12675 {
12676   char buf[MSG_SIZ];
12677   char *bookHit = NULL;
12678
12679     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12680         return;
12681
12682
12683     if (gameMode == PlayFromGameFile ||
12684         gameMode == TwoMachinesPlay  ||
12685         gameMode == Training         ||
12686         gameMode == AnalyzeMode      ||
12687         gameMode == EndOfGame)
12688         EditGameEvent();
12689
12690     if (gameMode == EditPosition)
12691         EditPositionDone(TRUE);
12692
12693     if (WhiteOnMove(currentMove)) {
12694         DisplayError(_("It is not Black's turn"), 0);
12695         return;
12696     }
12697
12698     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12699       ExitAnalyzeMode();
12700
12701     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12702         gameMode == AnalyzeFile)
12703         TruncateGame();
12704
12705     ResurrectChessProgram();    /* in case it isn't running */
12706     gameMode = MachinePlaysBlack;
12707     pausing = FALSE;
12708     ModeHighlight();
12709     SetGameInfo();
12710     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12711     DisplayTitle(buf);
12712     if (first.sendName) {
12713       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12714       SendToProgram(buf, &first);
12715     }
12716     if (first.sendTime) {
12717       if (first.useColors) {
12718         SendToProgram("white\n", &first); /*gnu kludge*/
12719       }
12720       SendTimeRemaining(&first, FALSE);
12721     }
12722     if (first.useColors) {
12723       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12724     }
12725     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12726     SetMachineThinkingEnables();
12727     first.maybeThinking = TRUE;
12728     StartClocks();
12729
12730     if (appData.autoFlipView && flipView) {
12731       flipView = !flipView;
12732       DrawPosition(FALSE, NULL);
12733       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12734     }
12735     if(bookHit) { // [HGM] book: simulate book reply
12736         static char bookMove[MSG_SIZ]; // a bit generous?
12737
12738         programStats.nodes = programStats.depth = programStats.time =
12739         programStats.score = programStats.got_only_move = 0;
12740         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12741
12742         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12743         strcat(bookMove, bookHit);
12744         HandleMachineMove(bookMove, &first);
12745     }
12746 }
12747
12748
12749 void
12750 DisplayTwoMachinesTitle()
12751 {
12752     char buf[MSG_SIZ];
12753     if (appData.matchGames > 0) {
12754         if(appData.tourneyFile[0]) {
12755           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12756                    gameInfo.white, gameInfo.black,
12757                    nextGame+1, appData.matchGames+1,
12758                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12759         } else 
12760         if (first.twoMachinesColor[0] == 'w') {
12761           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12762                    gameInfo.white, gameInfo.black,
12763                    first.matchWins, second.matchWins,
12764                    matchGame - 1 - (first.matchWins + second.matchWins));
12765         } else {
12766           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12767                    gameInfo.white, gameInfo.black,
12768                    second.matchWins, first.matchWins,
12769                    matchGame - 1 - (first.matchWins + second.matchWins));
12770         }
12771     } else {
12772       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12773     }
12774     DisplayTitle(buf);
12775 }
12776
12777 void
12778 SettingsMenuIfReady()
12779 {
12780   if (second.lastPing != second.lastPong) {
12781     DisplayMessage("", _("Waiting for second chess program"));
12782     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12783     return;
12784   }
12785   ThawUI();
12786   DisplayMessage("", "");
12787   SettingsPopUp(&second);
12788 }
12789
12790 int
12791 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12792 {
12793     char buf[MSG_SIZ];
12794     if (cps->pr == NULL) {
12795         StartChessProgram(cps);
12796         if (cps->protocolVersion == 1) {
12797           retry();
12798         } else {
12799           /* kludge: allow timeout for initial "feature" command */
12800           FreezeUI();
12801           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12802           DisplayMessage("", buf);
12803           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12804         }
12805         return 1;
12806     }
12807     return 0;
12808 }
12809
12810 void
12811 TwoMachinesEvent P((void))
12812 {
12813     int i;
12814     char buf[MSG_SIZ];
12815     ChessProgramState *onmove;
12816     char *bookHit = NULL;
12817     static int stalling = 0;
12818     TimeMark now;
12819     long wait;
12820
12821     if (appData.noChessProgram) return;
12822
12823     switch (gameMode) {
12824       case TwoMachinesPlay:
12825         return;
12826       case MachinePlaysWhite:
12827       case MachinePlaysBlack:
12828         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12829             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12830             return;
12831         }
12832         /* fall through */
12833       case BeginningOfGame:
12834       case PlayFromGameFile:
12835       case EndOfGame:
12836         EditGameEvent();
12837         if (gameMode != EditGame) return;
12838         break;
12839       case EditPosition:
12840         EditPositionDone(TRUE);
12841         break;
12842       case AnalyzeMode:
12843       case AnalyzeFile:
12844         ExitAnalyzeMode();
12845         break;
12846       case EditGame:
12847       default:
12848         break;
12849     }
12850
12851 //    forwardMostMove = currentMove;
12852     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12853
12854     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12855
12856     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12857     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12858       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12859       return;
12860     }
12861     if(!stalling) {
12862       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12863       SendToProgram("force\n", &second);
12864       stalling = 1;
12865       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12866       return;
12867     }
12868     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12869     if(appData.matchPause>10000 || appData.matchPause<10)
12870                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12871     wait = SubtractTimeMarks(&now, &pauseStart);
12872     if(wait < appData.matchPause) {
12873         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12874         return;
12875     }
12876     stalling = 0;
12877     DisplayMessage("", "");
12878     if (startedFromSetupPosition) {
12879         SendBoard(&second, backwardMostMove);
12880     if (appData.debugMode) {
12881         fprintf(debugFP, "Two Machines\n");
12882     }
12883     }
12884     for (i = backwardMostMove; i < forwardMostMove; i++) {
12885         SendMoveToProgram(i, &second);
12886     }
12887
12888     gameMode = TwoMachinesPlay;
12889     pausing = FALSE;
12890     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12891     SetGameInfo();
12892     DisplayTwoMachinesTitle();
12893     firstMove = TRUE;
12894     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12895         onmove = &first;
12896     } else {
12897         onmove = &second;
12898     }
12899     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12900     SendToProgram(first.computerString, &first);
12901     if (first.sendName) {
12902       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12903       SendToProgram(buf, &first);
12904     }
12905     SendToProgram(second.computerString, &second);
12906     if (second.sendName) {
12907       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12908       SendToProgram(buf, &second);
12909     }
12910
12911     ResetClocks();
12912     if (!first.sendTime || !second.sendTime) {
12913         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12914         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12915     }
12916     if (onmove->sendTime) {
12917       if (onmove->useColors) {
12918         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12919       }
12920       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12921     }
12922     if (onmove->useColors) {
12923       SendToProgram(onmove->twoMachinesColor, onmove);
12924     }
12925     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12926 //    SendToProgram("go\n", onmove);
12927     onmove->maybeThinking = TRUE;
12928     SetMachineThinkingEnables();
12929
12930     StartClocks();
12931
12932     if(bookHit) { // [HGM] book: simulate book reply
12933         static char bookMove[MSG_SIZ]; // a bit generous?
12934
12935         programStats.nodes = programStats.depth = programStats.time =
12936         programStats.score = programStats.got_only_move = 0;
12937         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12938
12939         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12940         strcat(bookMove, bookHit);
12941         savedMessage = bookMove; // args for deferred call
12942         savedState = onmove;
12943         ScheduleDelayedEvent(DeferredBookMove, 1);
12944     }
12945 }
12946
12947 void
12948 TrainingEvent()
12949 {
12950     if (gameMode == Training) {
12951       SetTrainingModeOff();
12952       gameMode = PlayFromGameFile;
12953       DisplayMessage("", _("Training mode off"));
12954     } else {
12955       gameMode = Training;
12956       animateTraining = appData.animate;
12957
12958       /* make sure we are not already at the end of the game */
12959       if (currentMove < forwardMostMove) {
12960         SetTrainingModeOn();
12961         DisplayMessage("", _("Training mode on"));
12962       } else {
12963         gameMode = PlayFromGameFile;
12964         DisplayError(_("Already at end of game"), 0);
12965       }
12966     }
12967     ModeHighlight();
12968 }
12969
12970 void
12971 IcsClientEvent()
12972 {
12973     if (!appData.icsActive) return;
12974     switch (gameMode) {
12975       case IcsPlayingWhite:
12976       case IcsPlayingBlack:
12977       case IcsObserving:
12978       case IcsIdle:
12979       case BeginningOfGame:
12980       case IcsExamining:
12981         return;
12982
12983       case EditGame:
12984         break;
12985
12986       case EditPosition:
12987         EditPositionDone(TRUE);
12988         break;
12989
12990       case AnalyzeMode:
12991       case AnalyzeFile:
12992         ExitAnalyzeMode();
12993         break;
12994
12995       default:
12996         EditGameEvent();
12997         break;
12998     }
12999
13000     gameMode = IcsIdle;
13001     ModeHighlight();
13002     return;
13003 }
13004
13005
13006 void
13007 EditGameEvent()
13008 {
13009     int i;
13010
13011     switch (gameMode) {
13012       case Training:
13013         SetTrainingModeOff();
13014         break;
13015       case MachinePlaysWhite:
13016       case MachinePlaysBlack:
13017       case BeginningOfGame:
13018         SendToProgram("force\n", &first);
13019         SetUserThinkingEnables();
13020         break;
13021       case PlayFromGameFile:
13022         (void) StopLoadGameTimer();
13023         if (gameFileFP != NULL) {
13024             gameFileFP = NULL;
13025         }
13026         break;
13027       case EditPosition:
13028         EditPositionDone(TRUE);
13029         break;
13030       case AnalyzeMode:
13031       case AnalyzeFile:
13032         ExitAnalyzeMode();
13033         SendToProgram("force\n", &first);
13034         break;
13035       case TwoMachinesPlay:
13036         GameEnds(EndOfFile, NULL, GE_PLAYER);
13037         ResurrectChessProgram();
13038         SetUserThinkingEnables();
13039         break;
13040       case EndOfGame:
13041         ResurrectChessProgram();
13042         break;
13043       case IcsPlayingBlack:
13044       case IcsPlayingWhite:
13045         DisplayError(_("Warning: You are still playing a game"), 0);
13046         break;
13047       case IcsObserving:
13048         DisplayError(_("Warning: You are still observing a game"), 0);
13049         break;
13050       case IcsExamining:
13051         DisplayError(_("Warning: You are still examining a game"), 0);
13052         break;
13053       case IcsIdle:
13054         break;
13055       case EditGame:
13056       default:
13057         return;
13058     }
13059
13060     pausing = FALSE;
13061     StopClocks();
13062     first.offeredDraw = second.offeredDraw = 0;
13063
13064     if (gameMode == PlayFromGameFile) {
13065         whiteTimeRemaining = timeRemaining[0][currentMove];
13066         blackTimeRemaining = timeRemaining[1][currentMove];
13067         DisplayTitle("");
13068     }
13069
13070     if (gameMode == MachinePlaysWhite ||
13071         gameMode == MachinePlaysBlack ||
13072         gameMode == TwoMachinesPlay ||
13073         gameMode == EndOfGame) {
13074         i = forwardMostMove;
13075         while (i > currentMove) {
13076             SendToProgram("undo\n", &first);
13077             i--;
13078         }
13079         whiteTimeRemaining = timeRemaining[0][currentMove];
13080         blackTimeRemaining = timeRemaining[1][currentMove];
13081         DisplayBothClocks();
13082         if (whiteFlag || blackFlag) {
13083             whiteFlag = blackFlag = 0;
13084         }
13085         DisplayTitle("");
13086     }
13087
13088     gameMode = EditGame;
13089     ModeHighlight();
13090     SetGameInfo();
13091 }
13092
13093
13094 void
13095 EditPositionEvent()
13096 {
13097     if (gameMode == EditPosition) {
13098         EditGameEvent();
13099         return;
13100     }
13101
13102     EditGameEvent();
13103     if (gameMode != EditGame) return;
13104
13105     gameMode = EditPosition;
13106     ModeHighlight();
13107     SetGameInfo();
13108     if (currentMove > 0)
13109       CopyBoard(boards[0], boards[currentMove]);
13110
13111     blackPlaysFirst = !WhiteOnMove(currentMove);
13112     ResetClocks();
13113     currentMove = forwardMostMove = backwardMostMove = 0;
13114     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13115     DisplayMove(-1);
13116 }
13117
13118 void
13119 ExitAnalyzeMode()
13120 {
13121     /* [DM] icsEngineAnalyze - possible call from other functions */
13122     if (appData.icsEngineAnalyze) {
13123         appData.icsEngineAnalyze = FALSE;
13124
13125         DisplayMessage("",_("Close ICS engine analyze..."));
13126     }
13127     if (first.analysisSupport && first.analyzing) {
13128       SendToProgram("exit\n", &first);
13129       first.analyzing = FALSE;
13130     }
13131     thinkOutput[0] = NULLCHAR;
13132 }
13133
13134 void
13135 EditPositionDone(Boolean fakeRights)
13136 {
13137     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13138
13139     startedFromSetupPosition = TRUE;
13140     InitChessProgram(&first, FALSE);
13141     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13142       boards[0][EP_STATUS] = EP_NONE;
13143       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13144     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13145         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13146         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13147       } else boards[0][CASTLING][2] = NoRights;
13148     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13149         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13150         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13151       } else boards[0][CASTLING][5] = NoRights;
13152     }
13153     SendToProgram("force\n", &first);
13154     if (blackPlaysFirst) {
13155         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13156         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13157         currentMove = forwardMostMove = backwardMostMove = 1;
13158         CopyBoard(boards[1], boards[0]);
13159     } else {
13160         currentMove = forwardMostMove = backwardMostMove = 0;
13161     }
13162     SendBoard(&first, forwardMostMove);
13163     if (appData.debugMode) {
13164         fprintf(debugFP, "EditPosDone\n");
13165     }
13166     DisplayTitle("");
13167     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13168     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13169     gameMode = EditGame;
13170     ModeHighlight();
13171     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13172     ClearHighlights(); /* [AS] */
13173 }
13174
13175 /* Pause for `ms' milliseconds */
13176 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13177 void
13178 TimeDelay(ms)
13179      long ms;
13180 {
13181     TimeMark m1, m2;
13182
13183     GetTimeMark(&m1);
13184     do {
13185         GetTimeMark(&m2);
13186     } while (SubtractTimeMarks(&m2, &m1) < ms);
13187 }
13188
13189 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13190 void
13191 SendMultiLineToICS(buf)
13192      char *buf;
13193 {
13194     char temp[MSG_SIZ+1], *p;
13195     int len;
13196
13197     len = strlen(buf);
13198     if (len > MSG_SIZ)
13199       len = MSG_SIZ;
13200
13201     strncpy(temp, buf, len);
13202     temp[len] = 0;
13203
13204     p = temp;
13205     while (*p) {
13206         if (*p == '\n' || *p == '\r')
13207           *p = ' ';
13208         ++p;
13209     }
13210
13211     strcat(temp, "\n");
13212     SendToICS(temp);
13213     SendToPlayer(temp, strlen(temp));
13214 }
13215
13216 void
13217 SetWhiteToPlayEvent()
13218 {
13219     if (gameMode == EditPosition) {
13220         blackPlaysFirst = FALSE;
13221         DisplayBothClocks();    /* works because currentMove is 0 */
13222     } else if (gameMode == IcsExamining) {
13223         SendToICS(ics_prefix);
13224         SendToICS("tomove white\n");
13225     }
13226 }
13227
13228 void
13229 SetBlackToPlayEvent()
13230 {
13231     if (gameMode == EditPosition) {
13232         blackPlaysFirst = TRUE;
13233         currentMove = 1;        /* kludge */
13234         DisplayBothClocks();
13235         currentMove = 0;
13236     } else if (gameMode == IcsExamining) {
13237         SendToICS(ics_prefix);
13238         SendToICS("tomove black\n");
13239     }
13240 }
13241
13242 void
13243 EditPositionMenuEvent(selection, x, y)
13244      ChessSquare selection;
13245      int x, y;
13246 {
13247     char buf[MSG_SIZ];
13248     ChessSquare piece = boards[0][y][x];
13249
13250     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13251
13252     switch (selection) {
13253       case ClearBoard:
13254         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13255             SendToICS(ics_prefix);
13256             SendToICS("bsetup clear\n");
13257         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13258             SendToICS(ics_prefix);
13259             SendToICS("clearboard\n");
13260         } else {
13261             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13262                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13263                 for (y = 0; y < BOARD_HEIGHT; y++) {
13264                     if (gameMode == IcsExamining) {
13265                         if (boards[currentMove][y][x] != EmptySquare) {
13266                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13267                                     AAA + x, ONE + y);
13268                             SendToICS(buf);
13269                         }
13270                     } else {
13271                         boards[0][y][x] = p;
13272                     }
13273                 }
13274             }
13275         }
13276         if (gameMode == EditPosition) {
13277             DrawPosition(FALSE, boards[0]);
13278         }
13279         break;
13280
13281       case WhitePlay:
13282         SetWhiteToPlayEvent();
13283         break;
13284
13285       case BlackPlay:
13286         SetBlackToPlayEvent();
13287         break;
13288
13289       case EmptySquare:
13290         if (gameMode == IcsExamining) {
13291             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13292             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13293             SendToICS(buf);
13294         } else {
13295             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13296                 if(x == BOARD_LEFT-2) {
13297                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13298                     boards[0][y][1] = 0;
13299                 } else
13300                 if(x == BOARD_RGHT+1) {
13301                     if(y >= gameInfo.holdingsSize) break;
13302                     boards[0][y][BOARD_WIDTH-2] = 0;
13303                 } else break;
13304             }
13305             boards[0][y][x] = EmptySquare;
13306             DrawPosition(FALSE, boards[0]);
13307         }
13308         break;
13309
13310       case PromotePiece:
13311         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13312            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13313             selection = (ChessSquare) (PROMOTED piece);
13314         } else if(piece == EmptySquare) selection = WhiteSilver;
13315         else selection = (ChessSquare)((int)piece - 1);
13316         goto defaultlabel;
13317
13318       case DemotePiece:
13319         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13320            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13321             selection = (ChessSquare) (DEMOTED piece);
13322         } else if(piece == EmptySquare) selection = BlackSilver;
13323         else selection = (ChessSquare)((int)piece + 1);
13324         goto defaultlabel;
13325
13326       case WhiteQueen:
13327       case BlackQueen:
13328         if(gameInfo.variant == VariantShatranj ||
13329            gameInfo.variant == VariantXiangqi  ||
13330            gameInfo.variant == VariantCourier  ||
13331            gameInfo.variant == VariantMakruk     )
13332             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13333         goto defaultlabel;
13334
13335       case WhiteKing:
13336       case BlackKing:
13337         if(gameInfo.variant == VariantXiangqi)
13338             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13339         if(gameInfo.variant == VariantKnightmate)
13340             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13341       default:
13342         defaultlabel:
13343         if (gameMode == IcsExamining) {
13344             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13345             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13346                      PieceToChar(selection), AAA + x, ONE + y);
13347             SendToICS(buf);
13348         } else {
13349             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13350                 int n;
13351                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13352                     n = PieceToNumber(selection - BlackPawn);
13353                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13354                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13355                     boards[0][BOARD_HEIGHT-1-n][1]++;
13356                 } else
13357                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13358                     n = PieceToNumber(selection);
13359                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13360                     boards[0][n][BOARD_WIDTH-1] = selection;
13361                     boards[0][n][BOARD_WIDTH-2]++;
13362                 }
13363             } else
13364             boards[0][y][x] = selection;
13365             DrawPosition(TRUE, boards[0]);
13366         }
13367         break;
13368     }
13369 }
13370
13371
13372 void
13373 DropMenuEvent(selection, x, y)
13374      ChessSquare selection;
13375      int x, y;
13376 {
13377     ChessMove moveType;
13378
13379     switch (gameMode) {
13380       case IcsPlayingWhite:
13381       case MachinePlaysBlack:
13382         if (!WhiteOnMove(currentMove)) {
13383             DisplayMoveError(_("It is Black's turn"));
13384             return;
13385         }
13386         moveType = WhiteDrop;
13387         break;
13388       case IcsPlayingBlack:
13389       case MachinePlaysWhite:
13390         if (WhiteOnMove(currentMove)) {
13391             DisplayMoveError(_("It is White's turn"));
13392             return;
13393         }
13394         moveType = BlackDrop;
13395         break;
13396       case EditGame:
13397         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13398         break;
13399       default:
13400         return;
13401     }
13402
13403     if (moveType == BlackDrop && selection < BlackPawn) {
13404       selection = (ChessSquare) ((int) selection
13405                                  + (int) BlackPawn - (int) WhitePawn);
13406     }
13407     if (boards[currentMove][y][x] != EmptySquare) {
13408         DisplayMoveError(_("That square is occupied"));
13409         return;
13410     }
13411
13412     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13413 }
13414
13415 void
13416 AcceptEvent()
13417 {
13418     /* Accept a pending offer of any kind from opponent */
13419
13420     if (appData.icsActive) {
13421         SendToICS(ics_prefix);
13422         SendToICS("accept\n");
13423     } else if (cmailMsgLoaded) {
13424         if (currentMove == cmailOldMove &&
13425             commentList[cmailOldMove] != NULL &&
13426             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13427                    "Black offers a draw" : "White offers a draw")) {
13428             TruncateGame();
13429             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13430             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13431         } else {
13432             DisplayError(_("There is no pending offer on this move"), 0);
13433             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13434         }
13435     } else {
13436         /* Not used for offers from chess program */
13437     }
13438 }
13439
13440 void
13441 DeclineEvent()
13442 {
13443     /* Decline a pending offer of any kind from opponent */
13444
13445     if (appData.icsActive) {
13446         SendToICS(ics_prefix);
13447         SendToICS("decline\n");
13448     } else if (cmailMsgLoaded) {
13449         if (currentMove == cmailOldMove &&
13450             commentList[cmailOldMove] != NULL &&
13451             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13452                    "Black offers a draw" : "White offers a draw")) {
13453 #ifdef NOTDEF
13454             AppendComment(cmailOldMove, "Draw declined", TRUE);
13455             DisplayComment(cmailOldMove - 1, "Draw declined");
13456 #endif /*NOTDEF*/
13457         } else {
13458             DisplayError(_("There is no pending offer on this move"), 0);
13459         }
13460     } else {
13461         /* Not used for offers from chess program */
13462     }
13463 }
13464
13465 void
13466 RematchEvent()
13467 {
13468     /* Issue ICS rematch command */
13469     if (appData.icsActive) {
13470         SendToICS(ics_prefix);
13471         SendToICS("rematch\n");
13472     }
13473 }
13474
13475 void
13476 CallFlagEvent()
13477 {
13478     /* Call your opponent's flag (claim a win on time) */
13479     if (appData.icsActive) {
13480         SendToICS(ics_prefix);
13481         SendToICS("flag\n");
13482     } else {
13483         switch (gameMode) {
13484           default:
13485             return;
13486           case MachinePlaysWhite:
13487             if (whiteFlag) {
13488                 if (blackFlag)
13489                   GameEnds(GameIsDrawn, "Both players ran out of time",
13490                            GE_PLAYER);
13491                 else
13492                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13493             } else {
13494                 DisplayError(_("Your opponent is not out of time"), 0);
13495             }
13496             break;
13497           case MachinePlaysBlack:
13498             if (blackFlag) {
13499                 if (whiteFlag)
13500                   GameEnds(GameIsDrawn, "Both players ran out of time",
13501                            GE_PLAYER);
13502                 else
13503                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13504             } else {
13505                 DisplayError(_("Your opponent is not out of time"), 0);
13506             }
13507             break;
13508         }
13509     }
13510 }
13511
13512 void
13513 ClockClick(int which)
13514 {       // [HGM] code moved to back-end from winboard.c
13515         if(which) { // black clock
13516           if (gameMode == EditPosition || gameMode == IcsExamining) {
13517             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13518             SetBlackToPlayEvent();
13519           } else if (gameMode == EditGame || shiftKey) {
13520             AdjustClock(which, -1);
13521           } else if (gameMode == IcsPlayingWhite ||
13522                      gameMode == MachinePlaysBlack) {
13523             CallFlagEvent();
13524           }
13525         } else { // white clock
13526           if (gameMode == EditPosition || gameMode == IcsExamining) {
13527             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13528             SetWhiteToPlayEvent();
13529           } else if (gameMode == EditGame || shiftKey) {
13530             AdjustClock(which, -1);
13531           } else if (gameMode == IcsPlayingBlack ||
13532                    gameMode == MachinePlaysWhite) {
13533             CallFlagEvent();
13534           }
13535         }
13536 }
13537
13538 void
13539 DrawEvent()
13540 {
13541     /* Offer draw or accept pending draw offer from opponent */
13542
13543     if (appData.icsActive) {
13544         /* Note: tournament rules require draw offers to be
13545            made after you make your move but before you punch
13546            your clock.  Currently ICS doesn't let you do that;
13547            instead, you immediately punch your clock after making
13548            a move, but you can offer a draw at any time. */
13549
13550         SendToICS(ics_prefix);
13551         SendToICS("draw\n");
13552         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13553     } else if (cmailMsgLoaded) {
13554         if (currentMove == cmailOldMove &&
13555             commentList[cmailOldMove] != NULL &&
13556             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13557                    "Black offers a draw" : "White offers a draw")) {
13558             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13559             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13560         } else if (currentMove == cmailOldMove + 1) {
13561             char *offer = WhiteOnMove(cmailOldMove) ?
13562               "White offers a draw" : "Black offers a draw";
13563             AppendComment(currentMove, offer, TRUE);
13564             DisplayComment(currentMove - 1, offer);
13565             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13566         } else {
13567             DisplayError(_("You must make your move before offering a draw"), 0);
13568             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13569         }
13570     } else if (first.offeredDraw) {
13571         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13572     } else {
13573         if (first.sendDrawOffers) {
13574             SendToProgram("draw\n", &first);
13575             userOfferedDraw = TRUE;
13576         }
13577     }
13578 }
13579
13580 void
13581 AdjournEvent()
13582 {
13583     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13584
13585     if (appData.icsActive) {
13586         SendToICS(ics_prefix);
13587         SendToICS("adjourn\n");
13588     } else {
13589         /* Currently GNU Chess doesn't offer or accept Adjourns */
13590     }
13591 }
13592
13593
13594 void
13595 AbortEvent()
13596 {
13597     /* Offer Abort or accept pending Abort offer from opponent */
13598
13599     if (appData.icsActive) {
13600         SendToICS(ics_prefix);
13601         SendToICS("abort\n");
13602     } else {
13603         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13604     }
13605 }
13606
13607 void
13608 ResignEvent()
13609 {
13610     /* Resign.  You can do this even if it's not your turn. */
13611
13612     if (appData.icsActive) {
13613         SendToICS(ics_prefix);
13614         SendToICS("resign\n");
13615     } else {
13616         switch (gameMode) {
13617           case MachinePlaysWhite:
13618             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13619             break;
13620           case MachinePlaysBlack:
13621             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13622             break;
13623           case EditGame:
13624             if (cmailMsgLoaded) {
13625                 TruncateGame();
13626                 if (WhiteOnMove(cmailOldMove)) {
13627                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13628                 } else {
13629                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13630                 }
13631                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13632             }
13633             break;
13634           default:
13635             break;
13636         }
13637     }
13638 }
13639
13640
13641 void
13642 StopObservingEvent()
13643 {
13644     /* Stop observing current games */
13645     SendToICS(ics_prefix);
13646     SendToICS("unobserve\n");
13647 }
13648
13649 void
13650 StopExaminingEvent()
13651 {
13652     /* Stop observing current game */
13653     SendToICS(ics_prefix);
13654     SendToICS("unexamine\n");
13655 }
13656
13657 void
13658 ForwardInner(target)
13659      int target;
13660 {
13661     int limit;
13662
13663     if (appData.debugMode)
13664         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13665                 target, currentMove, forwardMostMove);
13666
13667     if (gameMode == EditPosition)
13668       return;
13669
13670     if (gameMode == PlayFromGameFile && !pausing)
13671       PauseEvent();
13672
13673     if (gameMode == IcsExamining && pausing)
13674       limit = pauseExamForwardMostMove;
13675     else
13676       limit = forwardMostMove;
13677
13678     if (target > limit) target = limit;
13679
13680     if (target > 0 && moveList[target - 1][0]) {
13681         int fromX, fromY, toX, toY;
13682         toX = moveList[target - 1][2] - AAA;
13683         toY = moveList[target - 1][3] - ONE;
13684         if (moveList[target - 1][1] == '@') {
13685             if (appData.highlightLastMove) {
13686                 SetHighlights(-1, -1, toX, toY);
13687             }
13688         } else {
13689             fromX = moveList[target - 1][0] - AAA;
13690             fromY = moveList[target - 1][1] - ONE;
13691             if (target == currentMove + 1) {
13692                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13693             }
13694             if (appData.highlightLastMove) {
13695                 SetHighlights(fromX, fromY, toX, toY);
13696             }
13697         }
13698     }
13699     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13700         gameMode == Training || gameMode == PlayFromGameFile ||
13701         gameMode == AnalyzeFile) {
13702         while (currentMove < target) {
13703             SendMoveToProgram(currentMove++, &first);
13704         }
13705     } else {
13706         currentMove = target;
13707     }
13708
13709     if (gameMode == EditGame || gameMode == EndOfGame) {
13710         whiteTimeRemaining = timeRemaining[0][currentMove];
13711         blackTimeRemaining = timeRemaining[1][currentMove];
13712     }
13713     DisplayBothClocks();
13714     DisplayMove(currentMove - 1);
13715     DrawPosition(FALSE, boards[currentMove]);
13716     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13717     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13718         DisplayComment(currentMove - 1, commentList[currentMove]);
13719     }
13720     DisplayBook(currentMove);
13721 }
13722
13723
13724 void
13725 ForwardEvent()
13726 {
13727     if (gameMode == IcsExamining && !pausing) {
13728         SendToICS(ics_prefix);
13729         SendToICS("forward\n");
13730     } else {
13731         ForwardInner(currentMove + 1);
13732     }
13733 }
13734
13735 void
13736 ToEndEvent()
13737 {
13738     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13739         /* to optimze, we temporarily turn off analysis mode while we feed
13740          * the remaining moves to the engine. Otherwise we get analysis output
13741          * after each move.
13742          */
13743         if (first.analysisSupport) {
13744           SendToProgram("exit\nforce\n", &first);
13745           first.analyzing = FALSE;
13746         }
13747     }
13748
13749     if (gameMode == IcsExamining && !pausing) {
13750         SendToICS(ics_prefix);
13751         SendToICS("forward 999999\n");
13752     } else {
13753         ForwardInner(forwardMostMove);
13754     }
13755
13756     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13757         /* we have fed all the moves, so reactivate analysis mode */
13758         SendToProgram("analyze\n", &first);
13759         first.analyzing = TRUE;
13760         /*first.maybeThinking = TRUE;*/
13761         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13762     }
13763 }
13764
13765 void
13766 BackwardInner(target)
13767      int target;
13768 {
13769     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13770
13771     if (appData.debugMode)
13772         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13773                 target, currentMove, forwardMostMove);
13774
13775     if (gameMode == EditPosition) return;
13776     if (currentMove <= backwardMostMove) {
13777         ClearHighlights();
13778         DrawPosition(full_redraw, boards[currentMove]);
13779         return;
13780     }
13781     if (gameMode == PlayFromGameFile && !pausing)
13782       PauseEvent();
13783
13784     if (moveList[target][0]) {
13785         int fromX, fromY, toX, toY;
13786         toX = moveList[target][2] - AAA;
13787         toY = moveList[target][3] - ONE;
13788         if (moveList[target][1] == '@') {
13789             if (appData.highlightLastMove) {
13790                 SetHighlights(-1, -1, toX, toY);
13791             }
13792         } else {
13793             fromX = moveList[target][0] - AAA;
13794             fromY = moveList[target][1] - ONE;
13795             if (target == currentMove - 1) {
13796                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13797             }
13798             if (appData.highlightLastMove) {
13799                 SetHighlights(fromX, fromY, toX, toY);
13800             }
13801         }
13802     }
13803     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13804         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13805         while (currentMove > target) {
13806             SendToProgram("undo\n", &first);
13807             currentMove--;
13808         }
13809     } else {
13810         currentMove = target;
13811     }
13812
13813     if (gameMode == EditGame || gameMode == EndOfGame) {
13814         whiteTimeRemaining = timeRemaining[0][currentMove];
13815         blackTimeRemaining = timeRemaining[1][currentMove];
13816     }
13817     DisplayBothClocks();
13818     DisplayMove(currentMove - 1);
13819     DrawPosition(full_redraw, boards[currentMove]);
13820     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13821     // [HGM] PV info: routine tests if comment empty
13822     DisplayComment(currentMove - 1, commentList[currentMove]);
13823     DisplayBook(currentMove);
13824 }
13825
13826 void
13827 BackwardEvent()
13828 {
13829     if (gameMode == IcsExamining && !pausing) {
13830         SendToICS(ics_prefix);
13831         SendToICS("backward\n");
13832     } else {
13833         BackwardInner(currentMove - 1);
13834     }
13835 }
13836
13837 void
13838 ToStartEvent()
13839 {
13840     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13841         /* to optimize, we temporarily turn off analysis mode while we undo
13842          * all the moves. Otherwise we get analysis output after each undo.
13843          */
13844         if (first.analysisSupport) {
13845           SendToProgram("exit\nforce\n", &first);
13846           first.analyzing = FALSE;
13847         }
13848     }
13849
13850     if (gameMode == IcsExamining && !pausing) {
13851         SendToICS(ics_prefix);
13852         SendToICS("backward 999999\n");
13853     } else {
13854         BackwardInner(backwardMostMove);
13855     }
13856
13857     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13858         /* we have fed all the moves, so reactivate analysis mode */
13859         SendToProgram("analyze\n", &first);
13860         first.analyzing = TRUE;
13861         /*first.maybeThinking = TRUE;*/
13862         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13863     }
13864 }
13865
13866 void
13867 ToNrEvent(int to)
13868 {
13869   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13870   if (to >= forwardMostMove) to = forwardMostMove;
13871   if (to <= backwardMostMove) to = backwardMostMove;
13872   if (to < currentMove) {
13873     BackwardInner(to);
13874   } else {
13875     ForwardInner(to);
13876   }
13877 }
13878
13879 void
13880 RevertEvent(Boolean annotate)
13881 {
13882     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13883         return;
13884     }
13885     if (gameMode != IcsExamining) {
13886         DisplayError(_("You are not examining a game"), 0);
13887         return;
13888     }
13889     if (pausing) {
13890         DisplayError(_("You can't revert while pausing"), 0);
13891         return;
13892     }
13893     SendToICS(ics_prefix);
13894     SendToICS("revert\n");
13895 }
13896
13897 void
13898 RetractMoveEvent()
13899 {
13900     switch (gameMode) {
13901       case MachinePlaysWhite:
13902       case MachinePlaysBlack:
13903         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13904             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13905             return;
13906         }
13907         if (forwardMostMove < 2) return;
13908         currentMove = forwardMostMove = forwardMostMove - 2;
13909         whiteTimeRemaining = timeRemaining[0][currentMove];
13910         blackTimeRemaining = timeRemaining[1][currentMove];
13911         DisplayBothClocks();
13912         DisplayMove(currentMove - 1);
13913         ClearHighlights();/*!! could figure this out*/
13914         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13915         SendToProgram("remove\n", &first);
13916         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13917         break;
13918
13919       case BeginningOfGame:
13920       default:
13921         break;
13922
13923       case IcsPlayingWhite:
13924       case IcsPlayingBlack:
13925         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13926             SendToICS(ics_prefix);
13927             SendToICS("takeback 2\n");
13928         } else {
13929             SendToICS(ics_prefix);
13930             SendToICS("takeback 1\n");
13931         }
13932         break;
13933     }
13934 }
13935
13936 void
13937 MoveNowEvent()
13938 {
13939     ChessProgramState *cps;
13940
13941     switch (gameMode) {
13942       case MachinePlaysWhite:
13943         if (!WhiteOnMove(forwardMostMove)) {
13944             DisplayError(_("It is your turn"), 0);
13945             return;
13946         }
13947         cps = &first;
13948         break;
13949       case MachinePlaysBlack:
13950         if (WhiteOnMove(forwardMostMove)) {
13951             DisplayError(_("It is your turn"), 0);
13952             return;
13953         }
13954         cps = &first;
13955         break;
13956       case TwoMachinesPlay:
13957         if (WhiteOnMove(forwardMostMove) ==
13958             (first.twoMachinesColor[0] == 'w')) {
13959             cps = &first;
13960         } else {
13961             cps = &second;
13962         }
13963         break;
13964       case BeginningOfGame:
13965       default:
13966         return;
13967     }
13968     SendToProgram("?\n", cps);
13969 }
13970
13971 void
13972 TruncateGameEvent()
13973 {
13974     EditGameEvent();
13975     if (gameMode != EditGame) return;
13976     TruncateGame();
13977 }
13978
13979 void
13980 TruncateGame()
13981 {
13982     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13983     if (forwardMostMove > currentMove) {
13984         if (gameInfo.resultDetails != NULL) {
13985             free(gameInfo.resultDetails);
13986             gameInfo.resultDetails = NULL;
13987             gameInfo.result = GameUnfinished;
13988         }
13989         forwardMostMove = currentMove;
13990         HistorySet(parseList, backwardMostMove, forwardMostMove,
13991                    currentMove-1);
13992     }
13993 }
13994
13995 void
13996 HintEvent()
13997 {
13998     if (appData.noChessProgram) return;
13999     switch (gameMode) {
14000       case MachinePlaysWhite:
14001         if (WhiteOnMove(forwardMostMove)) {
14002             DisplayError(_("Wait until your turn"), 0);
14003             return;
14004         }
14005         break;
14006       case BeginningOfGame:
14007       case MachinePlaysBlack:
14008         if (!WhiteOnMove(forwardMostMove)) {
14009             DisplayError(_("Wait until your turn"), 0);
14010             return;
14011         }
14012         break;
14013       default:
14014         DisplayError(_("No hint available"), 0);
14015         return;
14016     }
14017     SendToProgram("hint\n", &first);
14018     hintRequested = TRUE;
14019 }
14020
14021 void
14022 BookEvent()
14023 {
14024     if (appData.noChessProgram) return;
14025     switch (gameMode) {
14026       case MachinePlaysWhite:
14027         if (WhiteOnMove(forwardMostMove)) {
14028             DisplayError(_("Wait until your turn"), 0);
14029             return;
14030         }
14031         break;
14032       case BeginningOfGame:
14033       case MachinePlaysBlack:
14034         if (!WhiteOnMove(forwardMostMove)) {
14035             DisplayError(_("Wait until your turn"), 0);
14036             return;
14037         }
14038         break;
14039       case EditPosition:
14040         EditPositionDone(TRUE);
14041         break;
14042       case TwoMachinesPlay:
14043         return;
14044       default:
14045         break;
14046     }
14047     SendToProgram("bk\n", &first);
14048     bookOutput[0] = NULLCHAR;
14049     bookRequested = TRUE;
14050 }
14051
14052 void
14053 AboutGameEvent()
14054 {
14055     char *tags = PGNTags(&gameInfo);
14056     TagsPopUp(tags, CmailMsg());
14057     free(tags);
14058 }
14059
14060 /* end button procedures */
14061
14062 void
14063 PrintPosition(fp, move)
14064      FILE *fp;
14065      int move;
14066 {
14067     int i, j;
14068
14069     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14070         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14071             char c = PieceToChar(boards[move][i][j]);
14072             fputc(c == 'x' ? '.' : c, fp);
14073             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14074         }
14075     }
14076     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14077       fprintf(fp, "white to play\n");
14078     else
14079       fprintf(fp, "black to play\n");
14080 }
14081
14082 void
14083 PrintOpponents(fp)
14084      FILE *fp;
14085 {
14086     if (gameInfo.white != NULL) {
14087         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14088     } else {
14089         fprintf(fp, "\n");
14090     }
14091 }
14092
14093 /* Find last component of program's own name, using some heuristics */
14094 void
14095 TidyProgramName(prog, host, buf)
14096      char *prog, *host, buf[MSG_SIZ];
14097 {
14098     char *p, *q;
14099     int local = (strcmp(host, "localhost") == 0);
14100     while (!local && (p = strchr(prog, ';')) != NULL) {
14101         p++;
14102         while (*p == ' ') p++;
14103         prog = p;
14104     }
14105     if (*prog == '"' || *prog == '\'') {
14106         q = strchr(prog + 1, *prog);
14107     } else {
14108         q = strchr(prog, ' ');
14109     }
14110     if (q == NULL) q = prog + strlen(prog);
14111     p = q;
14112     while (p >= prog && *p != '/' && *p != '\\') p--;
14113     p++;
14114     if(p == prog && *p == '"') p++;
14115     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14116     memcpy(buf, p, q - p);
14117     buf[q - p] = NULLCHAR;
14118     if (!local) {
14119         strcat(buf, "@");
14120         strcat(buf, host);
14121     }
14122 }
14123
14124 char *
14125 TimeControlTagValue()
14126 {
14127     char buf[MSG_SIZ];
14128     if (!appData.clockMode) {
14129       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14130     } else if (movesPerSession > 0) {
14131       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14132     } else if (timeIncrement == 0) {
14133       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14134     } else {
14135       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14136     }
14137     return StrSave(buf);
14138 }
14139
14140 void
14141 SetGameInfo()
14142 {
14143     /* This routine is used only for certain modes */
14144     VariantClass v = gameInfo.variant;
14145     ChessMove r = GameUnfinished;
14146     char *p = NULL;
14147
14148     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14149         r = gameInfo.result;
14150         p = gameInfo.resultDetails;
14151         gameInfo.resultDetails = NULL;
14152     }
14153     ClearGameInfo(&gameInfo);
14154     gameInfo.variant = v;
14155
14156     switch (gameMode) {
14157       case MachinePlaysWhite:
14158         gameInfo.event = StrSave( appData.pgnEventHeader );
14159         gameInfo.site = StrSave(HostName());
14160         gameInfo.date = PGNDate();
14161         gameInfo.round = StrSave("-");
14162         gameInfo.white = StrSave(first.tidy);
14163         gameInfo.black = StrSave(UserName());
14164         gameInfo.timeControl = TimeControlTagValue();
14165         break;
14166
14167       case MachinePlaysBlack:
14168         gameInfo.event = StrSave( appData.pgnEventHeader );
14169         gameInfo.site = StrSave(HostName());
14170         gameInfo.date = PGNDate();
14171         gameInfo.round = StrSave("-");
14172         gameInfo.white = StrSave(UserName());
14173         gameInfo.black = StrSave(first.tidy);
14174         gameInfo.timeControl = TimeControlTagValue();
14175         break;
14176
14177       case TwoMachinesPlay:
14178         gameInfo.event = StrSave( appData.pgnEventHeader );
14179         gameInfo.site = StrSave(HostName());
14180         gameInfo.date = PGNDate();
14181         if (roundNr > 0) {
14182             char buf[MSG_SIZ];
14183             snprintf(buf, MSG_SIZ, "%d", roundNr);
14184             gameInfo.round = StrSave(buf);
14185         } else {
14186             gameInfo.round = StrSave("-");
14187         }
14188         if (first.twoMachinesColor[0] == 'w') {
14189             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14190             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14191         } else {
14192             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14193             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14194         }
14195         gameInfo.timeControl = TimeControlTagValue();
14196         break;
14197
14198       case EditGame:
14199         gameInfo.event = StrSave("Edited game");
14200         gameInfo.site = StrSave(HostName());
14201         gameInfo.date = PGNDate();
14202         gameInfo.round = StrSave("-");
14203         gameInfo.white = StrSave("-");
14204         gameInfo.black = StrSave("-");
14205         gameInfo.result = r;
14206         gameInfo.resultDetails = p;
14207         break;
14208
14209       case EditPosition:
14210         gameInfo.event = StrSave("Edited position");
14211         gameInfo.site = StrSave(HostName());
14212         gameInfo.date = PGNDate();
14213         gameInfo.round = StrSave("-");
14214         gameInfo.white = StrSave("-");
14215         gameInfo.black = StrSave("-");
14216         break;
14217
14218       case IcsPlayingWhite:
14219       case IcsPlayingBlack:
14220       case IcsObserving:
14221       case IcsExamining:
14222         break;
14223
14224       case PlayFromGameFile:
14225         gameInfo.event = StrSave("Game from non-PGN file");
14226         gameInfo.site = StrSave(HostName());
14227         gameInfo.date = PGNDate();
14228         gameInfo.round = StrSave("-");
14229         gameInfo.white = StrSave("?");
14230         gameInfo.black = StrSave("?");
14231         break;
14232
14233       default:
14234         break;
14235     }
14236 }
14237
14238 void
14239 ReplaceComment(index, text)
14240      int index;
14241      char *text;
14242 {
14243     int len;
14244     char *p;
14245     float score;
14246
14247     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14248        pvInfoList[index-1].depth == len &&
14249        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14250        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14251     while (*text == '\n') text++;
14252     len = strlen(text);
14253     while (len > 0 && text[len - 1] == '\n') len--;
14254
14255     if (commentList[index] != NULL)
14256       free(commentList[index]);
14257
14258     if (len == 0) {
14259         commentList[index] = NULL;
14260         return;
14261     }
14262   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14263       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14264       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14265     commentList[index] = (char *) malloc(len + 2);
14266     strncpy(commentList[index], text, len);
14267     commentList[index][len] = '\n';
14268     commentList[index][len + 1] = NULLCHAR;
14269   } else {
14270     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14271     char *p;
14272     commentList[index] = (char *) malloc(len + 7);
14273     safeStrCpy(commentList[index], "{\n", 3);
14274     safeStrCpy(commentList[index]+2, text, len+1);
14275     commentList[index][len+2] = NULLCHAR;
14276     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14277     strcat(commentList[index], "\n}\n");
14278   }
14279 }
14280
14281 void
14282 CrushCRs(text)
14283      char *text;
14284 {
14285   char *p = text;
14286   char *q = text;
14287   char ch;
14288
14289   do {
14290     ch = *p++;
14291     if (ch == '\r') continue;
14292     *q++ = ch;
14293   } while (ch != '\0');
14294 }
14295
14296 void
14297 AppendComment(index, text, addBraces)
14298      int index;
14299      char *text;
14300      Boolean addBraces; // [HGM] braces: tells if we should add {}
14301 {
14302     int oldlen, len;
14303     char *old;
14304
14305 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14306     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14307
14308     CrushCRs(text);
14309     while (*text == '\n') text++;
14310     len = strlen(text);
14311     while (len > 0 && text[len - 1] == '\n') len--;
14312
14313     if (len == 0) return;
14314
14315     if (commentList[index] != NULL) {
14316         old = commentList[index];
14317         oldlen = strlen(old);
14318         while(commentList[index][oldlen-1] ==  '\n')
14319           commentList[index][--oldlen] = NULLCHAR;
14320         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14321         safeStrCpy(commentList[index], old, oldlen + len + 6);
14322         free(old);
14323         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14324         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14325           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14326           while (*text == '\n') { text++; len--; }
14327           commentList[index][--oldlen] = NULLCHAR;
14328       }
14329         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14330         else          strcat(commentList[index], "\n");
14331         strcat(commentList[index], text);
14332         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14333         else          strcat(commentList[index], "\n");
14334     } else {
14335         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14336         if(addBraces)
14337           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14338         else commentList[index][0] = NULLCHAR;
14339         strcat(commentList[index], text);
14340         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14341         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14342     }
14343 }
14344
14345 static char * FindStr( char * text, char * sub_text )
14346 {
14347     char * result = strstr( text, sub_text );
14348
14349     if( result != NULL ) {
14350         result += strlen( sub_text );
14351     }
14352
14353     return result;
14354 }
14355
14356 /* [AS] Try to extract PV info from PGN comment */
14357 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14358 char *GetInfoFromComment( int index, char * text )
14359 {
14360     char * sep = text, *p;
14361
14362     if( text != NULL && index > 0 ) {
14363         int score = 0;
14364         int depth = 0;
14365         int time = -1, sec = 0, deci;
14366         char * s_eval = FindStr( text, "[%eval " );
14367         char * s_emt = FindStr( text, "[%emt " );
14368
14369         if( s_eval != NULL || s_emt != NULL ) {
14370             /* New style */
14371             char delim;
14372
14373             if( s_eval != NULL ) {
14374                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14375                     return text;
14376                 }
14377
14378                 if( delim != ']' ) {
14379                     return text;
14380                 }
14381             }
14382
14383             if( s_emt != NULL ) {
14384             }
14385                 return text;
14386         }
14387         else {
14388             /* We expect something like: [+|-]nnn.nn/dd */
14389             int score_lo = 0;
14390
14391             if(*text != '{') return text; // [HGM] braces: must be normal comment
14392
14393             sep = strchr( text, '/' );
14394             if( sep == NULL || sep < (text+4) ) {
14395                 return text;
14396             }
14397
14398             p = text;
14399             if(p[1] == '(') { // comment starts with PV
14400                p = strchr(p, ')'); // locate end of PV
14401                if(p == NULL || sep < p+5) return text;
14402                // at this point we have something like "{(.*) +0.23/6 ..."
14403                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14404                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14405                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14406             }
14407             time = -1; sec = -1; deci = -1;
14408             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14409                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14410                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14411                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14412                 return text;
14413             }
14414
14415             if( score_lo < 0 || score_lo >= 100 ) {
14416                 return text;
14417             }
14418
14419             if(sec >= 0) time = 600*time + 10*sec; else
14420             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14421
14422             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14423
14424             /* [HGM] PV time: now locate end of PV info */
14425             while( *++sep >= '0' && *sep <= '9'); // strip depth
14426             if(time >= 0)
14427             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14428             if(sec >= 0)
14429             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14430             if(deci >= 0)
14431             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14432             while(*sep == ' ') sep++;
14433         }
14434
14435         if( depth <= 0 ) {
14436             return text;
14437         }
14438
14439         if( time < 0 ) {
14440             time = -1;
14441         }
14442
14443         pvInfoList[index-1].depth = depth;
14444         pvInfoList[index-1].score = score;
14445         pvInfoList[index-1].time  = 10*time; // centi-sec
14446         if(*sep == '}') *sep = 0; else *--sep = '{';
14447         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14448     }
14449     return sep;
14450 }
14451
14452 void
14453 SendToProgram(message, cps)
14454      char *message;
14455      ChessProgramState *cps;
14456 {
14457     int count, outCount, error;
14458     char buf[MSG_SIZ];
14459
14460     if (cps->pr == NULL) return;
14461     Attention(cps);
14462
14463     if (appData.debugMode) {
14464         TimeMark now;
14465         GetTimeMark(&now);
14466         fprintf(debugFP, "%ld >%-6s: %s",
14467                 SubtractTimeMarks(&now, &programStartTime),
14468                 cps->which, message);
14469     }
14470
14471     count = strlen(message);
14472     outCount = OutputToProcess(cps->pr, message, count, &error);
14473     if (outCount < count && !exiting
14474                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14475       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14476       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14477         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14478             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14479                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14480                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14481                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14482             } else {
14483                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14484                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14485                 gameInfo.result = res;
14486             }
14487             gameInfo.resultDetails = StrSave(buf);
14488         }
14489         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14490         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14491     }
14492 }
14493
14494 void
14495 ReceiveFromProgram(isr, closure, message, count, error)
14496      InputSourceRef isr;
14497      VOIDSTAR closure;
14498      char *message;
14499      int count;
14500      int error;
14501 {
14502     char *end_str;
14503     char buf[MSG_SIZ];
14504     ChessProgramState *cps = (ChessProgramState *)closure;
14505
14506     if (isr != cps->isr) return; /* Killed intentionally */
14507     if (count <= 0) {
14508         if (count == 0) {
14509             RemoveInputSource(cps->isr);
14510             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14511             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14512                     _(cps->which), cps->program);
14513         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14514                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14515                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14516                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14517                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14518                 } else {
14519                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14520                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14521                     gameInfo.result = res;
14522                 }
14523                 gameInfo.resultDetails = StrSave(buf);
14524             }
14525             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14526             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14527         } else {
14528             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14529                     _(cps->which), cps->program);
14530             RemoveInputSource(cps->isr);
14531
14532             /* [AS] Program is misbehaving badly... kill it */
14533             if( count == -2 ) {
14534                 DestroyChildProcess( cps->pr, 9 );
14535                 cps->pr = NoProc;
14536             }
14537
14538             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14539         }
14540         return;
14541     }
14542
14543     if ((end_str = strchr(message, '\r')) != NULL)
14544       *end_str = NULLCHAR;
14545     if ((end_str = strchr(message, '\n')) != NULL)
14546       *end_str = NULLCHAR;
14547
14548     if (appData.debugMode) {
14549         TimeMark now; int print = 1;
14550         char *quote = ""; char c; int i;
14551
14552         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14553                 char start = message[0];
14554                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14555                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14556                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14557                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14558                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14559                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14560                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14561                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14562                    sscanf(message, "hint: %c", &c)!=1 && 
14563                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14564                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14565                     print = (appData.engineComments >= 2);
14566                 }
14567                 message[0] = start; // restore original message
14568         }
14569         if(print) {
14570                 GetTimeMark(&now);
14571                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14572                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14573                         quote,
14574                         message);
14575         }
14576     }
14577
14578     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14579     if (appData.icsEngineAnalyze) {
14580         if (strstr(message, "whisper") != NULL ||
14581              strstr(message, "kibitz") != NULL ||
14582             strstr(message, "tellics") != NULL) return;
14583     }
14584
14585     HandleMachineMove(message, cps);
14586 }
14587
14588
14589 void
14590 SendTimeControl(cps, mps, tc, inc, sd, st)
14591      ChessProgramState *cps;
14592      int mps, inc, sd, st;
14593      long tc;
14594 {
14595     char buf[MSG_SIZ];
14596     int seconds;
14597
14598     if( timeControl_2 > 0 ) {
14599         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14600             tc = timeControl_2;
14601         }
14602     }
14603     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14604     inc /= cps->timeOdds;
14605     st  /= cps->timeOdds;
14606
14607     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14608
14609     if (st > 0) {
14610       /* Set exact time per move, normally using st command */
14611       if (cps->stKludge) {
14612         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14613         seconds = st % 60;
14614         if (seconds == 0) {
14615           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14616         } else {
14617           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14618         }
14619       } else {
14620         snprintf(buf, MSG_SIZ, "st %d\n", st);
14621       }
14622     } else {
14623       /* Set conventional or incremental time control, using level command */
14624       if (seconds == 0) {
14625         /* Note old gnuchess bug -- minutes:seconds used to not work.
14626            Fixed in later versions, but still avoid :seconds
14627            when seconds is 0. */
14628         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14629       } else {
14630         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14631                  seconds, inc/1000.);
14632       }
14633     }
14634     SendToProgram(buf, cps);
14635
14636     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14637     /* Orthogonally, limit search to given depth */
14638     if (sd > 0) {
14639       if (cps->sdKludge) {
14640         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14641       } else {
14642         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14643       }
14644       SendToProgram(buf, cps);
14645     }
14646
14647     if(cps->nps >= 0) { /* [HGM] nps */
14648         if(cps->supportsNPS == FALSE)
14649           cps->nps = -1; // don't use if engine explicitly says not supported!
14650         else {
14651           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14652           SendToProgram(buf, cps);
14653         }
14654     }
14655 }
14656
14657 ChessProgramState *WhitePlayer()
14658 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14659 {
14660     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14661        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14662         return &second;
14663     return &first;
14664 }
14665
14666 void
14667 SendTimeRemaining(cps, machineWhite)
14668      ChessProgramState *cps;
14669      int /*boolean*/ machineWhite;
14670 {
14671     char message[MSG_SIZ];
14672     long time, otime;
14673
14674     /* Note: this routine must be called when the clocks are stopped
14675        or when they have *just* been set or switched; otherwise
14676        it will be off by the time since the current tick started.
14677     */
14678     if (machineWhite) {
14679         time = whiteTimeRemaining / 10;
14680         otime = blackTimeRemaining / 10;
14681     } else {
14682         time = blackTimeRemaining / 10;
14683         otime = whiteTimeRemaining / 10;
14684     }
14685     /* [HGM] translate opponent's time by time-odds factor */
14686     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14687     if (appData.debugMode) {
14688         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14689     }
14690
14691     if (time <= 0) time = 1;
14692     if (otime <= 0) otime = 1;
14693
14694     snprintf(message, MSG_SIZ, "time %ld\n", time);
14695     SendToProgram(message, cps);
14696
14697     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14698     SendToProgram(message, cps);
14699 }
14700
14701 int
14702 BoolFeature(p, name, loc, cps)
14703      char **p;
14704      char *name;
14705      int *loc;
14706      ChessProgramState *cps;
14707 {
14708   char buf[MSG_SIZ];
14709   int len = strlen(name);
14710   int val;
14711
14712   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14713     (*p) += len + 1;
14714     sscanf(*p, "%d", &val);
14715     *loc = (val != 0);
14716     while (**p && **p != ' ')
14717       (*p)++;
14718     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14719     SendToProgram(buf, cps);
14720     return TRUE;
14721   }
14722   return FALSE;
14723 }
14724
14725 int
14726 IntFeature(p, name, loc, cps)
14727      char **p;
14728      char *name;
14729      int *loc;
14730      ChessProgramState *cps;
14731 {
14732   char buf[MSG_SIZ];
14733   int len = strlen(name);
14734   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14735     (*p) += len + 1;
14736     sscanf(*p, "%d", loc);
14737     while (**p && **p != ' ') (*p)++;
14738     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14739     SendToProgram(buf, cps);
14740     return TRUE;
14741   }
14742   return FALSE;
14743 }
14744
14745 int
14746 StringFeature(p, name, loc, cps)
14747      char **p;
14748      char *name;
14749      char loc[];
14750      ChessProgramState *cps;
14751 {
14752   char buf[MSG_SIZ];
14753   int len = strlen(name);
14754   if (strncmp((*p), name, len) == 0
14755       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14756     (*p) += len + 2;
14757     sscanf(*p, "%[^\"]", loc);
14758     while (**p && **p != '\"') (*p)++;
14759     if (**p == '\"') (*p)++;
14760     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14761     SendToProgram(buf, cps);
14762     return TRUE;
14763   }
14764   return FALSE;
14765 }
14766
14767 int
14768 ParseOption(Option *opt, ChessProgramState *cps)
14769 // [HGM] options: process the string that defines an engine option, and determine
14770 // name, type, default value, and allowed value range
14771 {
14772         char *p, *q, buf[MSG_SIZ];
14773         int n, min = (-1)<<31, max = 1<<31, def;
14774
14775         if(p = strstr(opt->name, " -spin ")) {
14776             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14777             if(max < min) max = min; // enforce consistency
14778             if(def < min) def = min;
14779             if(def > max) def = max;
14780             opt->value = def;
14781             opt->min = min;
14782             opt->max = max;
14783             opt->type = Spin;
14784         } else if((p = strstr(opt->name, " -slider "))) {
14785             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14786             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14787             if(max < min) max = min; // enforce consistency
14788             if(def < min) def = min;
14789             if(def > max) def = max;
14790             opt->value = def;
14791             opt->min = min;
14792             opt->max = max;
14793             opt->type = Spin; // Slider;
14794         } else if((p = strstr(opt->name, " -string "))) {
14795             opt->textValue = p+9;
14796             opt->type = TextBox;
14797         } else if((p = strstr(opt->name, " -file "))) {
14798             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14799             opt->textValue = p+7;
14800             opt->type = FileName; // FileName;
14801         } else if((p = strstr(opt->name, " -path "))) {
14802             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14803             opt->textValue = p+7;
14804             opt->type = PathName; // PathName;
14805         } else if(p = strstr(opt->name, " -check ")) {
14806             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14807             opt->value = (def != 0);
14808             opt->type = CheckBox;
14809         } else if(p = strstr(opt->name, " -combo ")) {
14810             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14811             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14812             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14813             opt->value = n = 0;
14814             while(q = StrStr(q, " /// ")) {
14815                 n++; *q = 0;    // count choices, and null-terminate each of them
14816                 q += 5;
14817                 if(*q == '*') { // remember default, which is marked with * prefix
14818                     q++;
14819                     opt->value = n;
14820                 }
14821                 cps->comboList[cps->comboCnt++] = q;
14822             }
14823             cps->comboList[cps->comboCnt++] = NULL;
14824             opt->max = n + 1;
14825             opt->type = ComboBox;
14826         } else if(p = strstr(opt->name, " -button")) {
14827             opt->type = Button;
14828         } else if(p = strstr(opt->name, " -save")) {
14829             opt->type = SaveButton;
14830         } else return FALSE;
14831         *p = 0; // terminate option name
14832         // now look if the command-line options define a setting for this engine option.
14833         if(cps->optionSettings && cps->optionSettings[0])
14834             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14835         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14836           snprintf(buf, MSG_SIZ, "option %s", p);
14837                 if(p = strstr(buf, ",")) *p = 0;
14838                 if(q = strchr(buf, '=')) switch(opt->type) {
14839                     case ComboBox:
14840                         for(n=0; n<opt->max; n++)
14841                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14842                         break;
14843                     case TextBox:
14844                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14845                         break;
14846                     case Spin:
14847                     case CheckBox:
14848                         opt->value = atoi(q+1);
14849                     default:
14850                         break;
14851                 }
14852                 strcat(buf, "\n");
14853                 SendToProgram(buf, cps);
14854         }
14855         return TRUE;
14856 }
14857
14858 void
14859 FeatureDone(cps, val)
14860      ChessProgramState* cps;
14861      int val;
14862 {
14863   DelayedEventCallback cb = GetDelayedEvent();
14864   if ((cb == InitBackEnd3 && cps == &first) ||
14865       (cb == SettingsMenuIfReady && cps == &second) ||
14866       (cb == LoadEngine) ||
14867       (cb == TwoMachinesEventIfReady)) {
14868     CancelDelayedEvent();
14869     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14870   }
14871   cps->initDone = val;
14872 }
14873
14874 /* Parse feature command from engine */
14875 void
14876 ParseFeatures(args, cps)
14877      char* args;
14878      ChessProgramState *cps;
14879 {
14880   char *p = args;
14881   char *q;
14882   int val;
14883   char buf[MSG_SIZ];
14884
14885   for (;;) {
14886     while (*p == ' ') p++;
14887     if (*p == NULLCHAR) return;
14888
14889     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14890     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14891     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14892     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14893     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14894     if (BoolFeature(&p, "reuse", &val, cps)) {
14895       /* Engine can disable reuse, but can't enable it if user said no */
14896       if (!val) cps->reuse = FALSE;
14897       continue;
14898     }
14899     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14900     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14901       if (gameMode == TwoMachinesPlay) {
14902         DisplayTwoMachinesTitle();
14903       } else {
14904         DisplayTitle("");
14905       }
14906       continue;
14907     }
14908     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14909     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14910     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14911     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14912     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14913     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14914     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14915     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14916     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14917     if (IntFeature(&p, "done", &val, cps)) {
14918       FeatureDone(cps, val);
14919       continue;
14920     }
14921     /* Added by Tord: */
14922     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14923     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14924     /* End of additions by Tord */
14925
14926     /* [HGM] added features: */
14927     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14928     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14929     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14930     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14931     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14932     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14933     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14934         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14935           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14936             SendToProgram(buf, cps);
14937             continue;
14938         }
14939         if(cps->nrOptions >= MAX_OPTIONS) {
14940             cps->nrOptions--;
14941             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14942             DisplayError(buf, 0);
14943         }
14944         continue;
14945     }
14946     /* End of additions by HGM */
14947
14948     /* unknown feature: complain and skip */
14949     q = p;
14950     while (*q && *q != '=') q++;
14951     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14952     SendToProgram(buf, cps);
14953     p = q;
14954     if (*p == '=') {
14955       p++;
14956       if (*p == '\"') {
14957         p++;
14958         while (*p && *p != '\"') p++;
14959         if (*p == '\"') p++;
14960       } else {
14961         while (*p && *p != ' ') p++;
14962       }
14963     }
14964   }
14965
14966 }
14967
14968 void
14969 PeriodicUpdatesEvent(newState)
14970      int newState;
14971 {
14972     if (newState == appData.periodicUpdates)
14973       return;
14974
14975     appData.periodicUpdates=newState;
14976
14977     /* Display type changes, so update it now */
14978 //    DisplayAnalysis();
14979
14980     /* Get the ball rolling again... */
14981     if (newState) {
14982         AnalysisPeriodicEvent(1);
14983         StartAnalysisClock();
14984     }
14985 }
14986
14987 void
14988 PonderNextMoveEvent(newState)
14989      int newState;
14990 {
14991     if (newState == appData.ponderNextMove) return;
14992     if (gameMode == EditPosition) EditPositionDone(TRUE);
14993     if (newState) {
14994         SendToProgram("hard\n", &first);
14995         if (gameMode == TwoMachinesPlay) {
14996             SendToProgram("hard\n", &second);
14997         }
14998     } else {
14999         SendToProgram("easy\n", &first);
15000         thinkOutput[0] = NULLCHAR;
15001         if (gameMode == TwoMachinesPlay) {
15002             SendToProgram("easy\n", &second);
15003         }
15004     }
15005     appData.ponderNextMove = newState;
15006 }
15007
15008 void
15009 NewSettingEvent(option, feature, command, value)
15010      char *command;
15011      int option, value, *feature;
15012 {
15013     char buf[MSG_SIZ];
15014
15015     if (gameMode == EditPosition) EditPositionDone(TRUE);
15016     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15017     if(feature == NULL || *feature) SendToProgram(buf, &first);
15018     if (gameMode == TwoMachinesPlay) {
15019         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15020     }
15021 }
15022
15023 void
15024 ShowThinkingEvent()
15025 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15026 {
15027     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15028     int newState = appData.showThinking
15029         // [HGM] thinking: other features now need thinking output as well
15030         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15031
15032     if (oldState == newState) return;
15033     oldState = newState;
15034     if (gameMode == EditPosition) EditPositionDone(TRUE);
15035     if (oldState) {
15036         SendToProgram("post\n", &first);
15037         if (gameMode == TwoMachinesPlay) {
15038             SendToProgram("post\n", &second);
15039         }
15040     } else {
15041         SendToProgram("nopost\n", &first);
15042         thinkOutput[0] = NULLCHAR;
15043         if (gameMode == TwoMachinesPlay) {
15044             SendToProgram("nopost\n", &second);
15045         }
15046     }
15047 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15048 }
15049
15050 void
15051 AskQuestionEvent(title, question, replyPrefix, which)
15052      char *title; char *question; char *replyPrefix; char *which;
15053 {
15054   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15055   if (pr == NoProc) return;
15056   AskQuestion(title, question, replyPrefix, pr);
15057 }
15058
15059 void
15060 TypeInEvent(char firstChar)
15061 {
15062     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15063         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15064         gameMode == AnalyzeMode || gameMode == EditGame || 
15065         gameMode == EditPosition || gameMode == IcsExamining ||
15066         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15067         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15068                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15069                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15070         gameMode == Training) PopUpMoveDialog(firstChar);
15071 }
15072
15073 void
15074 TypeInDoneEvent(char *move)
15075 {
15076         Board board;
15077         int n, fromX, fromY, toX, toY;
15078         char promoChar;
15079         ChessMove moveType;
15080
15081         // [HGM] FENedit
15082         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15083                 EditPositionPasteFEN(move);
15084                 return;
15085         }
15086         // [HGM] movenum: allow move number to be typed in any mode
15087         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15088           ToNrEvent(2*n-1);
15089           return;
15090         }
15091
15092       if (gameMode != EditGame && currentMove != forwardMostMove && 
15093         gameMode != Training) {
15094         DisplayMoveError(_("Displayed move is not current"));
15095       } else {
15096         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15097           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15098         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15099         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15100           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15101           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15102         } else {
15103           DisplayMoveError(_("Could not parse move"));
15104         }
15105       }
15106 }
15107
15108 void
15109 DisplayMove(moveNumber)
15110      int moveNumber;
15111 {
15112     char message[MSG_SIZ];
15113     char res[MSG_SIZ];
15114     char cpThinkOutput[MSG_SIZ];
15115
15116     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15117
15118     if (moveNumber == forwardMostMove - 1 ||
15119         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15120
15121         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15122
15123         if (strchr(cpThinkOutput, '\n')) {
15124             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15125         }
15126     } else {
15127         *cpThinkOutput = NULLCHAR;
15128     }
15129
15130     /* [AS] Hide thinking from human user */
15131     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15132         *cpThinkOutput = NULLCHAR;
15133         if( thinkOutput[0] != NULLCHAR ) {
15134             int i;
15135
15136             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15137                 cpThinkOutput[i] = '.';
15138             }
15139             cpThinkOutput[i] = NULLCHAR;
15140             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15141         }
15142     }
15143
15144     if (moveNumber == forwardMostMove - 1 &&
15145         gameInfo.resultDetails != NULL) {
15146         if (gameInfo.resultDetails[0] == NULLCHAR) {
15147           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15148         } else {
15149           snprintf(res, MSG_SIZ, " {%s} %s",
15150                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15151         }
15152     } else {
15153         res[0] = NULLCHAR;
15154     }
15155
15156     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15157         DisplayMessage(res, cpThinkOutput);
15158     } else {
15159       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15160                 WhiteOnMove(moveNumber) ? " " : ".. ",
15161                 parseList[moveNumber], res);
15162         DisplayMessage(message, cpThinkOutput);
15163     }
15164 }
15165
15166 void
15167 DisplayComment(moveNumber, text)
15168      int moveNumber;
15169      char *text;
15170 {
15171     char title[MSG_SIZ];
15172     char buf[8000]; // comment can be long!
15173     int score, depth;
15174
15175     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15176       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15177     } else {
15178       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15179               WhiteOnMove(moveNumber) ? " " : ".. ",
15180               parseList[moveNumber]);
15181     }
15182     // [HGM] PV info: display PV info together with (or as) comment
15183     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15184       if(text == NULL) text = "";
15185       score = pvInfoList[moveNumber].score;
15186       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15187               depth, (pvInfoList[moveNumber].time+50)/100, text);
15188       text = buf;
15189     }
15190     if (text != NULL && (appData.autoDisplayComment || commentUp))
15191         CommentPopUp(title, text);
15192 }
15193
15194 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15195  * might be busy thinking or pondering.  It can be omitted if your
15196  * gnuchess is configured to stop thinking immediately on any user
15197  * input.  However, that gnuchess feature depends on the FIONREAD
15198  * ioctl, which does not work properly on some flavors of Unix.
15199  */
15200 void
15201 Attention(cps)
15202      ChessProgramState *cps;
15203 {
15204 #if ATTENTION
15205     if (!cps->useSigint) return;
15206     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15207     switch (gameMode) {
15208       case MachinePlaysWhite:
15209       case MachinePlaysBlack:
15210       case TwoMachinesPlay:
15211       case IcsPlayingWhite:
15212       case IcsPlayingBlack:
15213       case AnalyzeMode:
15214       case AnalyzeFile:
15215         /* Skip if we know it isn't thinking */
15216         if (!cps->maybeThinking) return;
15217         if (appData.debugMode)
15218           fprintf(debugFP, "Interrupting %s\n", cps->which);
15219         InterruptChildProcess(cps->pr);
15220         cps->maybeThinking = FALSE;
15221         break;
15222       default:
15223         break;
15224     }
15225 #endif /*ATTENTION*/
15226 }
15227
15228 int
15229 CheckFlags()
15230 {
15231     if (whiteTimeRemaining <= 0) {
15232         if (!whiteFlag) {
15233             whiteFlag = TRUE;
15234             if (appData.icsActive) {
15235                 if (appData.autoCallFlag &&
15236                     gameMode == IcsPlayingBlack && !blackFlag) {
15237                   SendToICS(ics_prefix);
15238                   SendToICS("flag\n");
15239                 }
15240             } else {
15241                 if (blackFlag) {
15242                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15243                 } else {
15244                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15245                     if (appData.autoCallFlag) {
15246                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15247                         return TRUE;
15248                     }
15249                 }
15250             }
15251         }
15252     }
15253     if (blackTimeRemaining <= 0) {
15254         if (!blackFlag) {
15255             blackFlag = TRUE;
15256             if (appData.icsActive) {
15257                 if (appData.autoCallFlag &&
15258                     gameMode == IcsPlayingWhite && !whiteFlag) {
15259                   SendToICS(ics_prefix);
15260                   SendToICS("flag\n");
15261                 }
15262             } else {
15263                 if (whiteFlag) {
15264                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15265                 } else {
15266                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15267                     if (appData.autoCallFlag) {
15268                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15269                         return TRUE;
15270                     }
15271                 }
15272             }
15273         }
15274     }
15275     return FALSE;
15276 }
15277
15278 void
15279 CheckTimeControl()
15280 {
15281     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15282         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15283
15284     /*
15285      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15286      */
15287     if ( !WhiteOnMove(forwardMostMove) ) {
15288         /* White made time control */
15289         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15290         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15291         /* [HGM] time odds: correct new time quota for time odds! */
15292                                             / WhitePlayer()->timeOdds;
15293         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15294     } else {
15295         lastBlack -= blackTimeRemaining;
15296         /* Black made time control */
15297         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15298                                             / WhitePlayer()->other->timeOdds;
15299         lastWhite = whiteTimeRemaining;
15300     }
15301 }
15302
15303 void
15304 DisplayBothClocks()
15305 {
15306     int wom = gameMode == EditPosition ?
15307       !blackPlaysFirst : WhiteOnMove(currentMove);
15308     DisplayWhiteClock(whiteTimeRemaining, wom);
15309     DisplayBlackClock(blackTimeRemaining, !wom);
15310 }
15311
15312
15313 /* Timekeeping seems to be a portability nightmare.  I think everyone
15314    has ftime(), but I'm really not sure, so I'm including some ifdefs
15315    to use other calls if you don't.  Clocks will be less accurate if
15316    you have neither ftime nor gettimeofday.
15317 */
15318
15319 /* VS 2008 requires the #include outside of the function */
15320 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15321 #include <sys/timeb.h>
15322 #endif
15323
15324 /* Get the current time as a TimeMark */
15325 void
15326 GetTimeMark(tm)
15327      TimeMark *tm;
15328 {
15329 #if HAVE_GETTIMEOFDAY
15330
15331     struct timeval timeVal;
15332     struct timezone timeZone;
15333
15334     gettimeofday(&timeVal, &timeZone);
15335     tm->sec = (long) timeVal.tv_sec;
15336     tm->ms = (int) (timeVal.tv_usec / 1000L);
15337
15338 #else /*!HAVE_GETTIMEOFDAY*/
15339 #if HAVE_FTIME
15340
15341 // include <sys/timeb.h> / moved to just above start of function
15342     struct timeb timeB;
15343
15344     ftime(&timeB);
15345     tm->sec = (long) timeB.time;
15346     tm->ms = (int) timeB.millitm;
15347
15348 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15349     tm->sec = (long) time(NULL);
15350     tm->ms = 0;
15351 #endif
15352 #endif
15353 }
15354
15355 /* Return the difference in milliseconds between two
15356    time marks.  We assume the difference will fit in a long!
15357 */
15358 long
15359 SubtractTimeMarks(tm2, tm1)
15360      TimeMark *tm2, *tm1;
15361 {
15362     return 1000L*(tm2->sec - tm1->sec) +
15363            (long) (tm2->ms - tm1->ms);
15364 }
15365
15366
15367 /*
15368  * Code to manage the game clocks.
15369  *
15370  * In tournament play, black starts the clock and then white makes a move.
15371  * We give the human user a slight advantage if he is playing white---the
15372  * clocks don't run until he makes his first move, so it takes zero time.
15373  * Also, we don't account for network lag, so we could get out of sync
15374  * with GNU Chess's clock -- but then, referees are always right.
15375  */
15376
15377 static TimeMark tickStartTM;
15378 static long intendedTickLength;
15379
15380 long
15381 NextTickLength(timeRemaining)
15382      long timeRemaining;
15383 {
15384     long nominalTickLength, nextTickLength;
15385
15386     if (timeRemaining > 0L && timeRemaining <= 10000L)
15387       nominalTickLength = 100L;
15388     else
15389       nominalTickLength = 1000L;
15390     nextTickLength = timeRemaining % nominalTickLength;
15391     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15392
15393     return nextTickLength;
15394 }
15395
15396 /* Adjust clock one minute up or down */
15397 void
15398 AdjustClock(Boolean which, int dir)
15399 {
15400     if(which) blackTimeRemaining += 60000*dir;
15401     else      whiteTimeRemaining += 60000*dir;
15402     DisplayBothClocks();
15403 }
15404
15405 /* Stop clocks and reset to a fresh time control */
15406 void
15407 ResetClocks()
15408 {
15409     (void) StopClockTimer();
15410     if (appData.icsActive) {
15411         whiteTimeRemaining = blackTimeRemaining = 0;
15412     } else if (searchTime) {
15413         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15414         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15415     } else { /* [HGM] correct new time quote for time odds */
15416         whiteTC = blackTC = fullTimeControlString;
15417         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15418         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15419     }
15420     if (whiteFlag || blackFlag) {
15421         DisplayTitle("");
15422         whiteFlag = blackFlag = FALSE;
15423     }
15424     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15425     DisplayBothClocks();
15426 }
15427
15428 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15429
15430 /* Decrement running clock by amount of time that has passed */
15431 void
15432 DecrementClocks()
15433 {
15434     long timeRemaining;
15435     long lastTickLength, fudge;
15436     TimeMark now;
15437
15438     if (!appData.clockMode) return;
15439     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15440
15441     GetTimeMark(&now);
15442
15443     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15444
15445     /* Fudge if we woke up a little too soon */
15446     fudge = intendedTickLength - lastTickLength;
15447     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15448
15449     if (WhiteOnMove(forwardMostMove)) {
15450         if(whiteNPS >= 0) lastTickLength = 0;
15451         timeRemaining = whiteTimeRemaining -= lastTickLength;
15452         if(timeRemaining < 0 && !appData.icsActive) {
15453             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15454             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15455                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15456                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15457             }
15458         }
15459         DisplayWhiteClock(whiteTimeRemaining - fudge,
15460                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15461     } else {
15462         if(blackNPS >= 0) lastTickLength = 0;
15463         timeRemaining = blackTimeRemaining -= lastTickLength;
15464         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15465             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15466             if(suddenDeath) {
15467                 blackStartMove = forwardMostMove;
15468                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15469             }
15470         }
15471         DisplayBlackClock(blackTimeRemaining - fudge,
15472                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15473     }
15474     if (CheckFlags()) return;
15475
15476     tickStartTM = now;
15477     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15478     StartClockTimer(intendedTickLength);
15479
15480     /* if the time remaining has fallen below the alarm threshold, sound the
15481      * alarm. if the alarm has sounded and (due to a takeback or time control
15482      * with increment) the time remaining has increased to a level above the
15483      * threshold, reset the alarm so it can sound again.
15484      */
15485
15486     if (appData.icsActive && appData.icsAlarm) {
15487
15488         /* make sure we are dealing with the user's clock */
15489         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15490                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15491            )) return;
15492
15493         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15494             alarmSounded = FALSE;
15495         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15496             PlayAlarmSound();
15497             alarmSounded = TRUE;
15498         }
15499     }
15500 }
15501
15502
15503 /* A player has just moved, so stop the previously running
15504    clock and (if in clock mode) start the other one.
15505    We redisplay both clocks in case we're in ICS mode, because
15506    ICS gives us an update to both clocks after every move.
15507    Note that this routine is called *after* forwardMostMove
15508    is updated, so the last fractional tick must be subtracted
15509    from the color that is *not* on move now.
15510 */
15511 void
15512 SwitchClocks(int newMoveNr)
15513 {
15514     long lastTickLength;
15515     TimeMark now;
15516     int flagged = FALSE;
15517
15518     GetTimeMark(&now);
15519
15520     if (StopClockTimer() && appData.clockMode) {
15521         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15522         if (!WhiteOnMove(forwardMostMove)) {
15523             if(blackNPS >= 0) lastTickLength = 0;
15524             blackTimeRemaining -= lastTickLength;
15525            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15526 //         if(pvInfoList[forwardMostMove].time == -1)
15527                  pvInfoList[forwardMostMove].time =               // use GUI time
15528                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15529         } else {
15530            if(whiteNPS >= 0) lastTickLength = 0;
15531            whiteTimeRemaining -= lastTickLength;
15532            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15533 //         if(pvInfoList[forwardMostMove].time == -1)
15534                  pvInfoList[forwardMostMove].time =
15535                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15536         }
15537         flagged = CheckFlags();
15538     }
15539     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15540     CheckTimeControl();
15541
15542     if (flagged || !appData.clockMode) return;
15543
15544     switch (gameMode) {
15545       case MachinePlaysBlack:
15546       case MachinePlaysWhite:
15547       case BeginningOfGame:
15548         if (pausing) return;
15549         break;
15550
15551       case EditGame:
15552       case PlayFromGameFile:
15553       case IcsExamining:
15554         return;
15555
15556       default:
15557         break;
15558     }
15559
15560     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15561         if(WhiteOnMove(forwardMostMove))
15562              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15563         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15564     }
15565
15566     tickStartTM = now;
15567     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15568       whiteTimeRemaining : blackTimeRemaining);
15569     StartClockTimer(intendedTickLength);
15570 }
15571
15572
15573 /* Stop both clocks */
15574 void
15575 StopClocks()
15576 {
15577     long lastTickLength;
15578     TimeMark now;
15579
15580     if (!StopClockTimer()) return;
15581     if (!appData.clockMode) return;
15582
15583     GetTimeMark(&now);
15584
15585     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15586     if (WhiteOnMove(forwardMostMove)) {
15587         if(whiteNPS >= 0) lastTickLength = 0;
15588         whiteTimeRemaining -= lastTickLength;
15589         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15590     } else {
15591         if(blackNPS >= 0) lastTickLength = 0;
15592         blackTimeRemaining -= lastTickLength;
15593         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15594     }
15595     CheckFlags();
15596 }
15597
15598 /* Start clock of player on move.  Time may have been reset, so
15599    if clock is already running, stop and restart it. */
15600 void
15601 StartClocks()
15602 {
15603     (void) StopClockTimer(); /* in case it was running already */
15604     DisplayBothClocks();
15605     if (CheckFlags()) return;
15606
15607     if (!appData.clockMode) return;
15608     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15609
15610     GetTimeMark(&tickStartTM);
15611     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15612       whiteTimeRemaining : blackTimeRemaining);
15613
15614    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15615     whiteNPS = blackNPS = -1;
15616     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15617        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15618         whiteNPS = first.nps;
15619     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15620        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15621         blackNPS = first.nps;
15622     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15623         whiteNPS = second.nps;
15624     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15625         blackNPS = second.nps;
15626     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15627
15628     StartClockTimer(intendedTickLength);
15629 }
15630
15631 char *
15632 TimeString(ms)
15633      long ms;
15634 {
15635     long second, minute, hour, day;
15636     char *sign = "";
15637     static char buf[32];
15638
15639     if (ms > 0 && ms <= 9900) {
15640       /* convert milliseconds to tenths, rounding up */
15641       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15642
15643       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15644       return buf;
15645     }
15646
15647     /* convert milliseconds to seconds, rounding up */
15648     /* use floating point to avoid strangeness of integer division
15649        with negative dividends on many machines */
15650     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15651
15652     if (second < 0) {
15653         sign = "-";
15654         second = -second;
15655     }
15656
15657     day = second / (60 * 60 * 24);
15658     second = second % (60 * 60 * 24);
15659     hour = second / (60 * 60);
15660     second = second % (60 * 60);
15661     minute = second / 60;
15662     second = second % 60;
15663
15664     if (day > 0)
15665       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15666               sign, day, hour, minute, second);
15667     else if (hour > 0)
15668       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15669     else
15670       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15671
15672     return buf;
15673 }
15674
15675
15676 /*
15677  * This is necessary because some C libraries aren't ANSI C compliant yet.
15678  */
15679 char *
15680 StrStr(string, match)
15681      char *string, *match;
15682 {
15683     int i, length;
15684
15685     length = strlen(match);
15686
15687     for (i = strlen(string) - length; i >= 0; i--, string++)
15688       if (!strncmp(match, string, length))
15689         return string;
15690
15691     return NULL;
15692 }
15693
15694 char *
15695 StrCaseStr(string, match)
15696      char *string, *match;
15697 {
15698     int i, j, length;
15699
15700     length = strlen(match);
15701
15702     for (i = strlen(string) - length; i >= 0; i--, string++) {
15703         for (j = 0; j < length; j++) {
15704             if (ToLower(match[j]) != ToLower(string[j]))
15705               break;
15706         }
15707         if (j == length) return string;
15708     }
15709
15710     return NULL;
15711 }
15712
15713 #ifndef _amigados
15714 int
15715 StrCaseCmp(s1, s2)
15716      char *s1, *s2;
15717 {
15718     char c1, c2;
15719
15720     for (;;) {
15721         c1 = ToLower(*s1++);
15722         c2 = ToLower(*s2++);
15723         if (c1 > c2) return 1;
15724         if (c1 < c2) return -1;
15725         if (c1 == NULLCHAR) return 0;
15726     }
15727 }
15728
15729
15730 int
15731 ToLower(c)
15732      int c;
15733 {
15734     return isupper(c) ? tolower(c) : c;
15735 }
15736
15737
15738 int
15739 ToUpper(c)
15740      int c;
15741 {
15742     return islower(c) ? toupper(c) : c;
15743 }
15744 #endif /* !_amigados    */
15745
15746 char *
15747 StrSave(s)
15748      char *s;
15749 {
15750   char *ret;
15751
15752   if ((ret = (char *) malloc(strlen(s) + 1)))
15753     {
15754       safeStrCpy(ret, s, strlen(s)+1);
15755     }
15756   return ret;
15757 }
15758
15759 char *
15760 StrSavePtr(s, savePtr)
15761      char *s, **savePtr;
15762 {
15763     if (*savePtr) {
15764         free(*savePtr);
15765     }
15766     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15767       safeStrCpy(*savePtr, s, strlen(s)+1);
15768     }
15769     return(*savePtr);
15770 }
15771
15772 char *
15773 PGNDate()
15774 {
15775     time_t clock;
15776     struct tm *tm;
15777     char buf[MSG_SIZ];
15778
15779     clock = time((time_t *)NULL);
15780     tm = localtime(&clock);
15781     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15782             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15783     return StrSave(buf);
15784 }
15785
15786
15787 char *
15788 PositionToFEN(move, overrideCastling)
15789      int move;
15790      char *overrideCastling;
15791 {
15792     int i, j, fromX, fromY, toX, toY;
15793     int whiteToPlay;
15794     char buf[128];
15795     char *p, *q;
15796     int emptycount;
15797     ChessSquare piece;
15798
15799     whiteToPlay = (gameMode == EditPosition) ?
15800       !blackPlaysFirst : (move % 2 == 0);
15801     p = buf;
15802
15803     /* Piece placement data */
15804     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15805         emptycount = 0;
15806         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15807             if (boards[move][i][j] == EmptySquare) {
15808                 emptycount++;
15809             } else { ChessSquare piece = boards[move][i][j];
15810                 if (emptycount > 0) {
15811                     if(emptycount<10) /* [HGM] can be >= 10 */
15812                         *p++ = '0' + emptycount;
15813                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15814                     emptycount = 0;
15815                 }
15816                 if(PieceToChar(piece) == '+') {
15817                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15818                     *p++ = '+';
15819                     piece = (ChessSquare)(DEMOTED piece);
15820                 }
15821                 *p++ = PieceToChar(piece);
15822                 if(p[-1] == '~') {
15823                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15824                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15825                     *p++ = '~';
15826                 }
15827             }
15828         }
15829         if (emptycount > 0) {
15830             if(emptycount<10) /* [HGM] can be >= 10 */
15831                 *p++ = '0' + emptycount;
15832             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15833             emptycount = 0;
15834         }
15835         *p++ = '/';
15836     }
15837     *(p - 1) = ' ';
15838
15839     /* [HGM] print Crazyhouse or Shogi holdings */
15840     if( gameInfo.holdingsWidth ) {
15841         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15842         q = p;
15843         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15844             piece = boards[move][i][BOARD_WIDTH-1];
15845             if( piece != EmptySquare )
15846               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15847                   *p++ = PieceToChar(piece);
15848         }
15849         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15850             piece = boards[move][BOARD_HEIGHT-i-1][0];
15851             if( piece != EmptySquare )
15852               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15853                   *p++ = PieceToChar(piece);
15854         }
15855
15856         if( q == p ) *p++ = '-';
15857         *p++ = ']';
15858         *p++ = ' ';
15859     }
15860
15861     /* Active color */
15862     *p++ = whiteToPlay ? 'w' : 'b';
15863     *p++ = ' ';
15864
15865   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15866     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15867   } else {
15868   if(nrCastlingRights) {
15869      q = p;
15870      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15871        /* [HGM] write directly from rights */
15872            if(boards[move][CASTLING][2] != NoRights &&
15873               boards[move][CASTLING][0] != NoRights   )
15874                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15875            if(boards[move][CASTLING][2] != NoRights &&
15876               boards[move][CASTLING][1] != NoRights   )
15877                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15878            if(boards[move][CASTLING][5] != NoRights &&
15879               boards[move][CASTLING][3] != NoRights   )
15880                 *p++ = boards[move][CASTLING][3] + AAA;
15881            if(boards[move][CASTLING][5] != NoRights &&
15882               boards[move][CASTLING][4] != NoRights   )
15883                 *p++ = boards[move][CASTLING][4] + AAA;
15884      } else {
15885
15886         /* [HGM] write true castling rights */
15887         if( nrCastlingRights == 6 ) {
15888             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15889                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15890             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15891                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15892             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15893                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15894             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15895                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15896         }
15897      }
15898      if (q == p) *p++ = '-'; /* No castling rights */
15899      *p++ = ' ';
15900   }
15901
15902   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15903      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15904     /* En passant target square */
15905     if (move > backwardMostMove) {
15906         fromX = moveList[move - 1][0] - AAA;
15907         fromY = moveList[move - 1][1] - ONE;
15908         toX = moveList[move - 1][2] - AAA;
15909         toY = moveList[move - 1][3] - ONE;
15910         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15911             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15912             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15913             fromX == toX) {
15914             /* 2-square pawn move just happened */
15915             *p++ = toX + AAA;
15916             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15917         } else {
15918             *p++ = '-';
15919         }
15920     } else if(move == backwardMostMove) {
15921         // [HGM] perhaps we should always do it like this, and forget the above?
15922         if((signed char)boards[move][EP_STATUS] >= 0) {
15923             *p++ = boards[move][EP_STATUS] + AAA;
15924             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15925         } else {
15926             *p++ = '-';
15927         }
15928     } else {
15929         *p++ = '-';
15930     }
15931     *p++ = ' ';
15932   }
15933   }
15934
15935     /* [HGM] find reversible plies */
15936     {   int i = 0, j=move;
15937
15938         if (appData.debugMode) { int k;
15939             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15940             for(k=backwardMostMove; k<=forwardMostMove; k++)
15941                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15942
15943         }
15944
15945         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15946         if( j == backwardMostMove ) i += initialRulePlies;
15947         sprintf(p, "%d ", i);
15948         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15949     }
15950     /* Fullmove number */
15951     sprintf(p, "%d", (move / 2) + 1);
15952
15953     return StrSave(buf);
15954 }
15955
15956 Boolean
15957 ParseFEN(board, blackPlaysFirst, fen)
15958     Board board;
15959      int *blackPlaysFirst;
15960      char *fen;
15961 {
15962     int i, j;
15963     char *p, c;
15964     int emptycount;
15965     ChessSquare piece;
15966
15967     p = fen;
15968
15969     /* [HGM] by default clear Crazyhouse holdings, if present */
15970     if(gameInfo.holdingsWidth) {
15971        for(i=0; i<BOARD_HEIGHT; i++) {
15972            board[i][0]             = EmptySquare; /* black holdings */
15973            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15974            board[i][1]             = (ChessSquare) 0; /* black counts */
15975            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15976        }
15977     }
15978
15979     /* Piece placement data */
15980     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15981         j = 0;
15982         for (;;) {
15983             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15984                 if (*p == '/') p++;
15985                 emptycount = gameInfo.boardWidth - j;
15986                 while (emptycount--)
15987                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15988                 break;
15989 #if(BOARD_FILES >= 10)
15990             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15991                 p++; emptycount=10;
15992                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15993                 while (emptycount--)
15994                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15995 #endif
15996             } else if (isdigit(*p)) {
15997                 emptycount = *p++ - '0';
15998                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15999                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16000                 while (emptycount--)
16001                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16002             } else if (*p == '+' || isalpha(*p)) {
16003                 if (j >= gameInfo.boardWidth) return FALSE;
16004                 if(*p=='+') {
16005                     piece = CharToPiece(*++p);
16006                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16007                     piece = (ChessSquare) (PROMOTED piece ); p++;
16008                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16009                 } else piece = CharToPiece(*p++);
16010
16011                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16012                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16013                     piece = (ChessSquare) (PROMOTED piece);
16014                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16015                     p++;
16016                 }
16017                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16018             } else {
16019                 return FALSE;
16020             }
16021         }
16022     }
16023     while (*p == '/' || *p == ' ') p++;
16024
16025     /* [HGM] look for Crazyhouse holdings here */
16026     while(*p==' ') p++;
16027     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16028         if(*p == '[') p++;
16029         if(*p == '-' ) p++; /* empty holdings */ else {
16030             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16031             /* if we would allow FEN reading to set board size, we would   */
16032             /* have to add holdings and shift the board read so far here   */
16033             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16034                 p++;
16035                 if((int) piece >= (int) BlackPawn ) {
16036                     i = (int)piece - (int)BlackPawn;
16037                     i = PieceToNumber((ChessSquare)i);
16038                     if( i >= gameInfo.holdingsSize ) return FALSE;
16039                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16040                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16041                 } else {
16042                     i = (int)piece - (int)WhitePawn;
16043                     i = PieceToNumber((ChessSquare)i);
16044                     if( i >= gameInfo.holdingsSize ) return FALSE;
16045                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16046                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16047                 }
16048             }
16049         }
16050         if(*p == ']') p++;
16051     }
16052
16053     while(*p == ' ') p++;
16054
16055     /* Active color */
16056     c = *p++;
16057     if(appData.colorNickNames) {
16058       if( c == appData.colorNickNames[0] ) c = 'w'; else
16059       if( c == appData.colorNickNames[1] ) c = 'b';
16060     }
16061     switch (c) {
16062       case 'w':
16063         *blackPlaysFirst = FALSE;
16064         break;
16065       case 'b':
16066         *blackPlaysFirst = TRUE;
16067         break;
16068       default:
16069         return FALSE;
16070     }
16071
16072     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16073     /* return the extra info in global variiables             */
16074
16075     /* set defaults in case FEN is incomplete */
16076     board[EP_STATUS] = EP_UNKNOWN;
16077     for(i=0; i<nrCastlingRights; i++ ) {
16078         board[CASTLING][i] =
16079             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16080     }   /* assume possible unless obviously impossible */
16081     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16082     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16083     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16084                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16085     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16086     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16087     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16088                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16089     FENrulePlies = 0;
16090
16091     while(*p==' ') p++;
16092     if(nrCastlingRights) {
16093       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16094           /* castling indicator present, so default becomes no castlings */
16095           for(i=0; i<nrCastlingRights; i++ ) {
16096                  board[CASTLING][i] = NoRights;
16097           }
16098       }
16099       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16100              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16101              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16102              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16103         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16104
16105         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16106             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16107             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16108         }
16109         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16110             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16111         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16112                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16113         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16114                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16115         switch(c) {
16116           case'K':
16117               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16118               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16119               board[CASTLING][2] = whiteKingFile;
16120               break;
16121           case'Q':
16122               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16123               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16124               board[CASTLING][2] = whiteKingFile;
16125               break;
16126           case'k':
16127               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16128               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16129               board[CASTLING][5] = blackKingFile;
16130               break;
16131           case'q':
16132               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16133               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16134               board[CASTLING][5] = blackKingFile;
16135           case '-':
16136               break;
16137           default: /* FRC castlings */
16138               if(c >= 'a') { /* black rights */
16139                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16140                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16141                   if(i == BOARD_RGHT) break;
16142                   board[CASTLING][5] = i;
16143                   c -= AAA;
16144                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16145                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16146                   if(c > i)
16147                       board[CASTLING][3] = c;
16148                   else
16149                       board[CASTLING][4] = c;
16150               } else { /* white rights */
16151                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16152                     if(board[0][i] == WhiteKing) break;
16153                   if(i == BOARD_RGHT) break;
16154                   board[CASTLING][2] = i;
16155                   c -= AAA - 'a' + 'A';
16156                   if(board[0][c] >= WhiteKing) break;
16157                   if(c > i)
16158                       board[CASTLING][0] = c;
16159                   else
16160                       board[CASTLING][1] = c;
16161               }
16162         }
16163       }
16164       for(i=0; i<nrCastlingRights; i++)
16165         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16166     if (appData.debugMode) {
16167         fprintf(debugFP, "FEN castling rights:");
16168         for(i=0; i<nrCastlingRights; i++)
16169         fprintf(debugFP, " %d", board[CASTLING][i]);
16170         fprintf(debugFP, "\n");
16171     }
16172
16173       while(*p==' ') p++;
16174     }
16175
16176     /* read e.p. field in games that know e.p. capture */
16177     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16178        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16179       if(*p=='-') {
16180         p++; board[EP_STATUS] = EP_NONE;
16181       } else {
16182          char c = *p++ - AAA;
16183
16184          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16185          if(*p >= '0' && *p <='9') p++;
16186          board[EP_STATUS] = c;
16187       }
16188     }
16189
16190
16191     if(sscanf(p, "%d", &i) == 1) {
16192         FENrulePlies = i; /* 50-move ply counter */
16193         /* (The move number is still ignored)    */
16194     }
16195
16196     return TRUE;
16197 }
16198
16199 void
16200 EditPositionPasteFEN(char *fen)
16201 {
16202   if (fen != NULL) {
16203     Board initial_position;
16204
16205     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16206       DisplayError(_("Bad FEN position in clipboard"), 0);
16207       return ;
16208     } else {
16209       int savedBlackPlaysFirst = blackPlaysFirst;
16210       EditPositionEvent();
16211       blackPlaysFirst = savedBlackPlaysFirst;
16212       CopyBoard(boards[0], initial_position);
16213       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16214       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16215       DisplayBothClocks();
16216       DrawPosition(FALSE, boards[currentMove]);
16217     }
16218   }
16219 }
16220
16221 static char cseq[12] = "\\   ";
16222
16223 Boolean set_cont_sequence(char *new_seq)
16224 {
16225     int len;
16226     Boolean ret;
16227
16228     // handle bad attempts to set the sequence
16229         if (!new_seq)
16230                 return 0; // acceptable error - no debug
16231
16232     len = strlen(new_seq);
16233     ret = (len > 0) && (len < sizeof(cseq));
16234     if (ret)
16235       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16236     else if (appData.debugMode)
16237       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16238     return ret;
16239 }
16240
16241 /*
16242     reformat a source message so words don't cross the width boundary.  internal
16243     newlines are not removed.  returns the wrapped size (no null character unless
16244     included in source message).  If dest is NULL, only calculate the size required
16245     for the dest buffer.  lp argument indicats line position upon entry, and it's
16246     passed back upon exit.
16247 */
16248 int wrap(char *dest, char *src, int count, int width, int *lp)
16249 {
16250     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16251
16252     cseq_len = strlen(cseq);
16253     old_line = line = *lp;
16254     ansi = len = clen = 0;
16255
16256     for (i=0; i < count; i++)
16257     {
16258         if (src[i] == '\033')
16259             ansi = 1;
16260
16261         // if we hit the width, back up
16262         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16263         {
16264             // store i & len in case the word is too long
16265             old_i = i, old_len = len;
16266
16267             // find the end of the last word
16268             while (i && src[i] != ' ' && src[i] != '\n')
16269             {
16270                 i--;
16271                 len--;
16272             }
16273
16274             // word too long?  restore i & len before splitting it
16275             if ((old_i-i+clen) >= width)
16276             {
16277                 i = old_i;
16278                 len = old_len;
16279             }
16280
16281             // extra space?
16282             if (i && src[i-1] == ' ')
16283                 len--;
16284
16285             if (src[i] != ' ' && src[i] != '\n')
16286             {
16287                 i--;
16288                 if (len)
16289                     len--;
16290             }
16291
16292             // now append the newline and continuation sequence
16293             if (dest)
16294                 dest[len] = '\n';
16295             len++;
16296             if (dest)
16297                 strncpy(dest+len, cseq, cseq_len);
16298             len += cseq_len;
16299             line = cseq_len;
16300             clen = cseq_len;
16301             continue;
16302         }
16303
16304         if (dest)
16305             dest[len] = src[i];
16306         len++;
16307         if (!ansi)
16308             line++;
16309         if (src[i] == '\n')
16310             line = 0;
16311         if (src[i] == 'm')
16312             ansi = 0;
16313     }
16314     if (dest && appData.debugMode)
16315     {
16316         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16317             count, width, line, len, *lp);
16318         show_bytes(debugFP, src, count);
16319         fprintf(debugFP, "\ndest: ");
16320         show_bytes(debugFP, dest, len);
16321         fprintf(debugFP, "\n");
16322     }
16323     *lp = dest ? line : old_line;
16324
16325     return len;
16326 }
16327
16328 // [HGM] vari: routines for shelving variations
16329
16330 void
16331 PushInner(int firstMove, int lastMove)
16332 {
16333         int i, j, nrMoves = lastMove - firstMove;
16334
16335         // push current tail of game on stack
16336         savedResult[storedGames] = gameInfo.result;
16337         savedDetails[storedGames] = gameInfo.resultDetails;
16338         gameInfo.resultDetails = NULL;
16339         savedFirst[storedGames] = firstMove;
16340         savedLast [storedGames] = lastMove;
16341         savedFramePtr[storedGames] = framePtr;
16342         framePtr -= nrMoves; // reserve space for the boards
16343         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16344             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16345             for(j=0; j<MOVE_LEN; j++)
16346                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16347             for(j=0; j<2*MOVE_LEN; j++)
16348                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16349             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16350             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16351             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16352             pvInfoList[firstMove+i-1].depth = 0;
16353             commentList[framePtr+i] = commentList[firstMove+i];
16354             commentList[firstMove+i] = NULL;
16355         }
16356
16357         storedGames++;
16358         forwardMostMove = firstMove; // truncate game so we can start variation
16359 }
16360
16361 void
16362 PushTail(int firstMove, int lastMove)
16363 {
16364         if(appData.icsActive) { // only in local mode
16365                 forwardMostMove = currentMove; // mimic old ICS behavior
16366                 return;
16367         }
16368         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16369
16370         PushInner(firstMove, lastMove);
16371         if(storedGames == 1) GreyRevert(FALSE);
16372 }
16373
16374 void
16375 PopInner(Boolean annotate)
16376 {
16377         int i, j, nrMoves;
16378         char buf[8000], moveBuf[20];
16379
16380         storedGames--;
16381         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16382         nrMoves = savedLast[storedGames] - currentMove;
16383         if(annotate) {
16384                 int cnt = 10;
16385                 if(!WhiteOnMove(currentMove))
16386                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16387                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16388                 for(i=currentMove; i<forwardMostMove; i++) {
16389                         if(WhiteOnMove(i))
16390                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16391                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16392                         strcat(buf, moveBuf);
16393                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16394                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16395                 }
16396                 strcat(buf, ")");
16397         }
16398         for(i=1; i<=nrMoves; i++) { // copy last variation back
16399             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16400             for(j=0; j<MOVE_LEN; j++)
16401                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16402             for(j=0; j<2*MOVE_LEN; j++)
16403                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16404             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16405             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16406             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16407             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16408             commentList[currentMove+i] = commentList[framePtr+i];
16409             commentList[framePtr+i] = NULL;
16410         }
16411         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16412         framePtr = savedFramePtr[storedGames];
16413         gameInfo.result = savedResult[storedGames];
16414         if(gameInfo.resultDetails != NULL) {
16415             free(gameInfo.resultDetails);
16416       }
16417         gameInfo.resultDetails = savedDetails[storedGames];
16418         forwardMostMove = currentMove + nrMoves;
16419 }
16420
16421 Boolean
16422 PopTail(Boolean annotate)
16423 {
16424         if(appData.icsActive) return FALSE; // only in local mode
16425         if(!storedGames) return FALSE; // sanity
16426         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16427
16428         PopInner(annotate);
16429
16430         if(storedGames == 0) GreyRevert(TRUE);
16431         return TRUE;
16432 }
16433
16434 void
16435 CleanupTail()
16436 {       // remove all shelved variations
16437         int i;
16438         for(i=0; i<storedGames; i++) {
16439             if(savedDetails[i])
16440                 free(savedDetails[i]);
16441             savedDetails[i] = NULL;
16442         }
16443         for(i=framePtr; i<MAX_MOVES; i++) {
16444                 if(commentList[i]) free(commentList[i]);
16445                 commentList[i] = NULL;
16446         }
16447         framePtr = MAX_MOVES-1;
16448         storedGames = 0;
16449 }
16450
16451 void
16452 LoadVariation(int index, char *text)
16453 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16454         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16455         int level = 0, move;
16456
16457         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16458         // first find outermost bracketing variation
16459         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16460             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16461                 if(*p == '{') wait = '}'; else
16462                 if(*p == '[') wait = ']'; else
16463                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16464                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16465             }
16466             if(*p == wait) wait = NULLCHAR; // closing ]} found
16467             p++;
16468         }
16469         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16470         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16471         end[1] = NULLCHAR; // clip off comment beyond variation
16472         ToNrEvent(currentMove-1);
16473         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16474         // kludge: use ParsePV() to append variation to game
16475         move = currentMove;
16476         ParsePV(start, TRUE, TRUE);
16477         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16478         ClearPremoveHighlights();
16479         CommentPopDown();
16480         ToNrEvent(currentMove+1);
16481 }
16482