Implement sweep selection of promotion piece
[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 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy( char *dst, const char *src, size_t count )
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble(u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags(index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386     flags &= ~F_ALL_CASTLE_OK;
387     break;
388   default:
389     break;
390   }
391   return flags;
392 }
393
394 FILE *gameFileFP, *debugFP;
395
396 /*
397     [AS] Note: sometimes, the sscanf() function is used to parse the input
398     into a fixed-size buffer. Because of this, we must be prepared to
399     receive strings as long as the size of the input buffer, which is currently
400     set to 4K for Windows and 8K for the rest.
401     So, we must either allocate sufficiently large buffers here, or
402     reduce the size of the input buffer in the input reading part.
403 */
404
405 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
406 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
407 char thinkOutput1[MSG_SIZ*10];
408
409 ChessProgramState first, second;
410
411 /* premove variables */
412 int premoveToX = 0;
413 int premoveToY = 0;
414 int premoveFromX = 0;
415 int premoveFromY = 0;
416 int premovePromoChar = 0;
417 int gotPremove = 0;
418 Boolean alarmSounded;
419 /* end premove variables */
420
421 char *ics_prefix = "$";
422 int ics_type = ICS_GENERIC;
423
424 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
425 int pauseExamForwardMostMove = 0;
426 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
427 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
428 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
429 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
430 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
431 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
432 int whiteFlag = FALSE, blackFlag = FALSE;
433 int userOfferedDraw = FALSE;
434 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
435 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
436 int cmailMoveType[CMAIL_MAX_GAMES];
437 long ics_clock_paused = 0;
438 ProcRef icsPR = NoProc, cmailPR = NoProc;
439 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
440 GameMode gameMode = BeginningOfGame;
441 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
442 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
443 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
444 int hiddenThinkOutputState = 0; /* [AS] */
445 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
446 int adjudicateLossPlies = 6;
447 char white_holding[64], black_holding[64];
448 TimeMark lastNodeCountTime;
449 long lastNodeCount=0;
450 int shiftKey; // [HGM] set by mouse handler
451
452 int have_sent_ICS_logon = 0;
453 int movesPerSession;
454 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
455 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
456 long timeControl_2; /* [AS] Allow separate time controls */
457 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
458 long timeRemaining[2][MAX_MOVES];
459 int matchGame = 0;
460 TimeMark programStartTime;
461 char ics_handle[MSG_SIZ];
462 int have_set_title = 0;
463
464 /* animateTraining preserves the state of appData.animate
465  * when Training mode is activated. This allows the
466  * response to be animated when appData.animate == TRUE and
467  * appData.animateDragging == TRUE.
468  */
469 Boolean animateTraining;
470
471 GameInfo gameInfo;
472
473 AppData appData;
474
475 Board boards[MAX_MOVES];
476 /* [HGM] Following 7 needed for accurate legality tests: */
477 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
478 signed char  initialRights[BOARD_FILES];
479 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
480 int   initialRulePlies, FENrulePlies;
481 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
482 int loadFlag = 0;
483 int shuffleOpenings;
484 int mute; // mute all sounds
485
486 // [HGM] vari: next 12 to save and restore variations
487 #define MAX_VARIATIONS 10
488 int framePtr = MAX_MOVES-1; // points to free stack entry
489 int storedGames = 0;
490 int savedFirst[MAX_VARIATIONS];
491 int savedLast[MAX_VARIATIONS];
492 int savedFramePtr[MAX_VARIATIONS];
493 char *savedDetails[MAX_VARIATIONS];
494 ChessMove savedResult[MAX_VARIATIONS];
495
496 void PushTail P((int firstMove, int lastMove));
497 Boolean PopTail P((Boolean annotate));
498 void CleanupTail P((void));
499
500 ChessSquare  FIDEArray[2][BOARD_FILES] = {
501     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
504         BlackKing, BlackBishop, BlackKnight, BlackRook }
505 };
506
507 ChessSquare twoKingsArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackKing, BlackKnight, BlackRook }
512 };
513
514 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
516         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
517     { BlackRook, BlackMan, BlackBishop, BlackQueen,
518         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
519 };
520
521 ChessSquare SpartanArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
524     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
525         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
526 };
527
528 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
532         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
533 };
534
535 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
536     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
537         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
538     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
539         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
540 };
541
542 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
544         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackMan, BlackFerz,
546         BlackKing, BlackMan, BlackKnight, BlackRook }
547 };
548
549
550 #if (BOARD_FILES>=10)
551 ChessSquare ShogiArray[2][BOARD_FILES] = {
552     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
553         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
554     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
555         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
556 };
557
558 ChessSquare XiangqiArray[2][BOARD_FILES] = {
559     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
560         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
561     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
562         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
563 };
564
565 ChessSquare CapablancaArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
567         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
569         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
570 };
571
572 ChessSquare GreatArray[2][BOARD_FILES] = {
573     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
574         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
575     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
576         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
577 };
578
579 ChessSquare JanusArray[2][BOARD_FILES] = {
580     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
581         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
582     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
583         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
584 };
585
586 #ifdef GOTHIC
587 ChessSquare GothicArray[2][BOARD_FILES] = {
588     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
589         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
590     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
591         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
592 };
593 #else // !GOTHIC
594 #define GothicArray CapablancaArray
595 #endif // !GOTHIC
596
597 #ifdef FALCON
598 ChessSquare FalconArray[2][BOARD_FILES] = {
599     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
600         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
601     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
602         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
603 };
604 #else // !FALCON
605 #define FalconArray CapablancaArray
606 #endif // !FALCON
607
608 #else // !(BOARD_FILES>=10)
609 #define XiangqiPosition FIDEArray
610 #define CapablancaArray FIDEArray
611 #define GothicArray FIDEArray
612 #define GreatArray FIDEArray
613 #endif // !(BOARD_FILES>=10)
614
615 #if (BOARD_FILES>=12)
616 ChessSquare CourierArray[2][BOARD_FILES] = {
617     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
618         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
619     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
620         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
621 };
622 #else // !(BOARD_FILES>=12)
623 #define CourierArray CapablancaArray
624 #endif // !(BOARD_FILES>=12)
625
626
627 Board initialPosition;
628
629
630 /* Convert str to a rating. Checks for special cases of "----",
631
632    "++++", etc. Also strips ()'s */
633 int
634 string_to_rating(str)
635   char *str;
636 {
637   while(*str && !isdigit(*str)) ++str;
638   if (!*str)
639     return 0;   /* One of the special "no rating" cases */
640   else
641     return atoi(str);
642 }
643
644 void
645 ClearProgramStats()
646 {
647     /* Init programStats */
648     programStats.movelist[0] = 0;
649     programStats.depth = 0;
650     programStats.nr_moves = 0;
651     programStats.moves_left = 0;
652     programStats.nodes = 0;
653     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
654     programStats.score = 0;
655     programStats.got_only_move = 0;
656     programStats.got_fail = 0;
657     programStats.line_is_book = 0;
658 }
659
660 void
661 InitBackEnd1()
662 {
663     int matched, min, sec;
664
665     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
666     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
667
668     GetTimeMark(&programStartTime);
669     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
670
671     ClearProgramStats();
672     programStats.ok_to_send = 1;
673     programStats.seen_stat = 0;
674
675     /*
676      * Initialize game list
677      */
678     ListNew(&gameList);
679
680
681     /*
682      * Internet chess server status
683      */
684     if (appData.icsActive) {
685         appData.matchMode = FALSE;
686         appData.matchGames = 0;
687 #if ZIPPY
688         appData.noChessProgram = !appData.zippyPlay;
689 #else
690         appData.zippyPlay = FALSE;
691         appData.zippyTalk = FALSE;
692         appData.noChessProgram = TRUE;
693 #endif
694         if (*appData.icsHelper != NULLCHAR) {
695             appData.useTelnet = TRUE;
696             appData.telnetProgram = appData.icsHelper;
697         }
698     } else {
699         appData.zippyTalk = appData.zippyPlay = FALSE;
700     }
701
702     /* [AS] Initialize pv info list [HGM] and game state */
703     {
704         int i, j;
705
706         for( i=0; i<=framePtr; i++ ) {
707             pvInfoList[i].depth = -1;
708             boards[i][EP_STATUS] = EP_NONE;
709             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
710         }
711     }
712
713     /*
714      * Parse timeControl resource
715      */
716     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
717                           appData.movesPerSession)) {
718         char buf[MSG_SIZ];
719         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
720         DisplayFatalError(buf, 0, 2);
721     }
722
723     /*
724      * Parse searchTime resource
725      */
726     if (*appData.searchTime != NULLCHAR) {
727         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
728         if (matched == 1) {
729             searchTime = min * 60;
730         } else if (matched == 2) {
731             searchTime = min * 60 + sec;
732         } else {
733             char buf[MSG_SIZ];
734             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
735             DisplayFatalError(buf, 0, 2);
736         }
737     }
738
739     /* [AS] Adjudication threshold */
740     adjudicateLossThreshold = appData.adjudicateLossThreshold;
741
742     first.which = "first";
743     second.which = "second";
744     first.maybeThinking = second.maybeThinking = FALSE;
745     first.pr = second.pr = NoProc;
746     first.isr = second.isr = NULL;
747     first.sendTime = second.sendTime = 2;
748     first.sendDrawOffers = 1;
749     if (appData.firstPlaysBlack) {
750         first.twoMachinesColor = "black\n";
751         second.twoMachinesColor = "white\n";
752     } else {
753         first.twoMachinesColor = "white\n";
754         second.twoMachinesColor = "black\n";
755     }
756     first.program = appData.firstChessProgram;
757     second.program = appData.secondChessProgram;
758     first.host = appData.firstHost;
759     second.host = appData.secondHost;
760     first.dir = appData.firstDirectory;
761     second.dir = appData.secondDirectory;
762     first.other = &second;
763     second.other = &first;
764     first.initString = appData.initString;
765     second.initString = appData.secondInitString;
766     first.computerString = appData.firstComputerString;
767     second.computerString = appData.secondComputerString;
768     first.useSigint = second.useSigint = TRUE;
769     first.useSigterm = second.useSigterm = TRUE;
770     first.reuse = appData.reuseFirst;
771     second.reuse = appData.reuseSecond;
772     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
773     second.nps = appData.secondNPS;
774     first.useSetboard = second.useSetboard = FALSE;
775     first.useSAN = second.useSAN = FALSE;
776     first.usePing = second.usePing = FALSE;
777     first.lastPing = second.lastPing = 0;
778     first.lastPong = second.lastPong = 0;
779     first.usePlayother = second.usePlayother = FALSE;
780     first.useColors = second.useColors = TRUE;
781     first.useUsermove = second.useUsermove = FALSE;
782     first.sendICS = second.sendICS = FALSE;
783     first.sendName = second.sendName = appData.icsActive;
784     first.sdKludge = second.sdKludge = FALSE;
785     first.stKludge = second.stKludge = FALSE;
786     TidyProgramName(first.program, first.host, first.tidy);
787     TidyProgramName(second.program, second.host, second.tidy);
788     first.matchWins = second.matchWins = 0;
789     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
790     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
791     first.analysisSupport = second.analysisSupport = 2; /* detect */
792     first.analyzing = second.analyzing = FALSE;
793     first.initDone = second.initDone = FALSE;
794
795     /* New features added by Tord: */
796     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
797     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
798     /* End of new features added by Tord. */
799     first.fenOverride  = appData.fenOverride1;
800     second.fenOverride = appData.fenOverride2;
801
802     /* [HGM] time odds: set factor for each machine */
803     first.timeOdds  = appData.firstTimeOdds;
804     second.timeOdds = appData.secondTimeOdds;
805     { float norm = 1;
806         if(appData.timeOddsMode) {
807             norm = first.timeOdds;
808             if(norm > second.timeOdds) norm = second.timeOdds;
809         }
810         first.timeOdds /= norm;
811         second.timeOdds /= norm;
812     }
813
814     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
815     first.accumulateTC = appData.firstAccumulateTC;
816     second.accumulateTC = appData.secondAccumulateTC;
817     first.maxNrOfSessions = second.maxNrOfSessions = 1;
818
819     /* [HGM] debug */
820     first.debug = second.debug = FALSE;
821     first.supportsNPS = second.supportsNPS = UNKNOWN;
822
823     /* [HGM] options */
824     first.optionSettings  = appData.firstOptions;
825     second.optionSettings = appData.secondOptions;
826
827     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
828     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
829     first.isUCI = appData.firstIsUCI; /* [AS] */
830     second.isUCI = appData.secondIsUCI; /* [AS] */
831     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
832     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
833
834     if (appData.firstProtocolVersion > PROTOVER
835         || appData.firstProtocolVersion < 1)
836       {
837         char buf[MSG_SIZ];
838         int len;
839
840         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
841                        appData.firstProtocolVersion);
842         if( (len > MSG_SIZ) && appData.debugMode )
843           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
844
845         DisplayFatalError(buf, 0, 2);
846       }
847     else
848       {
849         first.protocolVersion = appData.firstProtocolVersion;
850       }
851
852     if (appData.secondProtocolVersion > PROTOVER
853         || appData.secondProtocolVersion < 1)
854       {
855         char buf[MSG_SIZ];
856         int len;
857
858         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
859                        appData.secondProtocolVersion);
860         if( (len > MSG_SIZ) && appData.debugMode )
861           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
862
863         DisplayFatalError(buf, 0, 2);
864       }
865     else
866       {
867         second.protocolVersion = appData.secondProtocolVersion;
868       }
869
870     if (appData.icsActive) {
871         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
872 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
873     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
874         appData.clockMode = FALSE;
875         first.sendTime = second.sendTime = 0;
876     }
877
878 #if ZIPPY
879     /* Override some settings from environment variables, for backward
880        compatibility.  Unfortunately it's not feasible to have the env
881        vars just set defaults, at least in xboard.  Ugh.
882     */
883     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
884       ZippyInit();
885     }
886 #endif
887
888     if (appData.noChessProgram) {
889         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
890         sprintf(programVersion, "%s", PACKAGE_STRING);
891     } else {
892       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
893       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
894       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
895     }
896
897     if (!appData.icsActive) {
898       char buf[MSG_SIZ];
899       int len;
900
901       /* Check for variants that are supported only in ICS mode,
902          or not at all.  Some that are accepted here nevertheless
903          have bugs; see comments below.
904       */
905       VariantClass variant = StringToVariant(appData.variant);
906       switch (variant) {
907       case VariantBughouse:     /* need four players and two boards */
908       case VariantKriegspiel:   /* need to hide pieces and move details */
909         /* case VariantFischeRandom: (Fabien: moved below) */
910         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
911         if( (len > MSG_SIZ) && appData.debugMode )
912           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
913
914         DisplayFatalError(buf, 0, 2);
915         return;
916
917       case VariantUnknown:
918       case VariantLoadable:
919       case Variant29:
920       case Variant30:
921       case Variant31:
922       case Variant32:
923       case Variant33:
924       case Variant34:
925       case Variant35:
926       case Variant36:
927       default:
928         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
929         if( (len > MSG_SIZ) && appData.debugMode )
930           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
931
932         DisplayFatalError(buf, 0, 2);
933         return;
934
935       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
936       case VariantFairy:      /* [HGM] TestLegality definitely off! */
937       case VariantGothic:     /* [HGM] should work */
938       case VariantCapablanca: /* [HGM] should work */
939       case VariantCourier:    /* [HGM] initial forced moves not implemented */
940       case VariantShogi:      /* [HGM] could still mate with pawn drop */
941       case VariantKnightmate: /* [HGM] should work */
942       case VariantCylinder:   /* [HGM] untested */
943       case VariantFalcon:     /* [HGM] untested */
944       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
945                                  offboard interposition not understood */
946       case VariantNormal:     /* definitely works! */
947       case VariantWildCastle: /* pieces not automatically shuffled */
948       case VariantNoCastle:   /* pieces not automatically shuffled */
949       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
950       case VariantLosers:     /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantSuicide:    /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantGiveaway:   /* should work except for win condition,
955                                  and doesn't know captures are mandatory */
956       case VariantTwoKings:   /* should work */
957       case VariantAtomic:     /* should work except for win condition */
958       case Variant3Check:     /* should work except for win condition */
959       case VariantShatranj:   /* should work except for all win conditions */
960       case VariantMakruk:     /* should work except for daw countdown */
961       case VariantBerolina:   /* might work if TestLegality is off */
962       case VariantCapaRandom: /* should work */
963       case VariantJanus:      /* should work */
964       case VariantSuper:      /* experimental */
965       case VariantGreat:      /* experimental, requires legality testing to be off */
966       case VariantSChess:     /* S-Chess, should work */
967       case VariantSpartan:    /* should work */
968         break;
969       }
970     }
971
972     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
973     InitEngineUCI( installDir, &second );
974 }
975
976 int NextIntegerFromString( char ** str, long * value )
977 {
978     int result = -1;
979     char * s = *str;
980
981     while( *s == ' ' || *s == '\t' ) {
982         s++;
983     }
984
985     *value = 0;
986
987     if( *s >= '0' && *s <= '9' ) {
988         while( *s >= '0' && *s <= '9' ) {
989             *value = *value * 10 + (*s - '0');
990             s++;
991         }
992
993         result = 0;
994     }
995
996     *str = s;
997
998     return result;
999 }
1000
1001 int NextTimeControlFromString( char ** str, long * value )
1002 {
1003     long temp;
1004     int result = NextIntegerFromString( str, &temp );
1005
1006     if( result == 0 ) {
1007         *value = temp * 60; /* Minutes */
1008         if( **str == ':' ) {
1009             (*str)++;
1010             result = NextIntegerFromString( str, &temp );
1011             *value += temp; /* Seconds */
1012         }
1013     }
1014
1015     return result;
1016 }
1017
1018 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1019 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1020     int result = -1, type = 0; long temp, temp2;
1021
1022     if(**str != ':') return -1; // old params remain in force!
1023     (*str)++;
1024     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1025     if( NextIntegerFromString( str, &temp ) ) return -1;
1026     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1027
1028     if(**str != '/') {
1029         /* time only: incremental or sudden-death time control */
1030         if(**str == '+') { /* increment follows; read it */
1031             (*str)++;
1032             if(**str == '!') type = *(*str)++; // Bronstein TC
1033             if(result = NextIntegerFromString( str, &temp2)) return -1;
1034             *inc = temp2 * 1000;
1035             if(**str == '.') { // read fraction of increment
1036                 char *start = ++(*str);
1037                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1038                 temp2 *= 1000;
1039                 while(start++ < *str) temp2 /= 10;
1040                 *inc += temp2;
1041             }
1042         } else *inc = 0;
1043         *moves = 0; *tc = temp * 1000; *incType = type;
1044         return 0;
1045     }
1046
1047     (*str)++; /* classical time control */
1048     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1049
1050     if(result == 0) {
1051         *moves = temp;
1052         *tc    = temp2 * 1000;
1053         *inc   = 0;
1054         *incType = type;
1055     }
1056     return result;
1057 }
1058
1059 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1060 {   /* [HGM] get time to add from the multi-session time-control string */
1061     int incType, moves=1; /* kludge to force reading of first session */
1062     long time, increment;
1063     char *s = tcString;
1064
1065     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1066     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1067     do {
1068         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1069         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1070         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1071         if(movenr == -1) return time;    /* last move before new session     */
1072         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1073         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1074         if(!moves) return increment;     /* current session is incremental   */
1075         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1076     } while(movenr >= -1);               /* try again for next session       */
1077
1078     return 0; // no new time quota on this move
1079 }
1080
1081 int
1082 ParseTimeControl(tc, ti, mps)
1083      char *tc;
1084      float ti;
1085      int mps;
1086 {
1087   long tc1;
1088   long tc2;
1089   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1090   int min, sec=0;
1091
1092   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1093   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1094       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1095   if(ti > 0) {
1096
1097     if(mps)
1098       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1099     else 
1100       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1101   } else {
1102     if(mps)
1103       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1104     else 
1105       snprintf(buf, MSG_SIZ, ":%s", mytc);
1106   }
1107   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1108   
1109   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1110     return FALSE;
1111   }
1112
1113   if( *tc == '/' ) {
1114     /* Parse second time control */
1115     tc++;
1116
1117     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1118       return FALSE;
1119     }
1120
1121     if( tc2 == 0 ) {
1122       return FALSE;
1123     }
1124
1125     timeControl_2 = tc2 * 1000;
1126   }
1127   else {
1128     timeControl_2 = 0;
1129   }
1130
1131   if( tc1 == 0 ) {
1132     return FALSE;
1133   }
1134
1135   timeControl = tc1 * 1000;
1136
1137   if (ti >= 0) {
1138     timeIncrement = ti * 1000;  /* convert to ms */
1139     movesPerSession = 0;
1140   } else {
1141     timeIncrement = 0;
1142     movesPerSession = mps;
1143   }
1144   return TRUE;
1145 }
1146
1147 void
1148 InitBackEnd2()
1149 {
1150     if (appData.debugMode) {
1151         fprintf(debugFP, "%s\n", programVersion);
1152     }
1153
1154     set_cont_sequence(appData.wrapContSeq);
1155     if (appData.matchGames > 0) {
1156         appData.matchMode = TRUE;
1157     } else if (appData.matchMode) {
1158         appData.matchGames = 1;
1159     }
1160     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1161         appData.matchGames = appData.sameColorGames;
1162     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1163         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1164         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1165     }
1166     Reset(TRUE, FALSE);
1167     if (appData.noChessProgram || first.protocolVersion == 1) {
1168       InitBackEnd3();
1169     } else {
1170       /* kludge: allow timeout for initial "feature" commands */
1171       FreezeUI();
1172       DisplayMessage("", _("Starting chess program"));
1173       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1174     }
1175 }
1176
1177 void
1178 InitBackEnd3 P((void))
1179 {
1180     GameMode initialMode;
1181     char buf[MSG_SIZ];
1182     int err, len;
1183
1184     InitChessProgram(&first, startedFromSetupPosition);
1185
1186     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1187         free(programVersion);
1188         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1189         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1190     }
1191
1192     if (appData.icsActive) {
1193 #ifdef WIN32
1194         /* [DM] Make a console window if needed [HGM] merged ifs */
1195         ConsoleCreate();
1196 #endif
1197         err = establish();
1198         if (err != 0)
1199           {
1200             if (*appData.icsCommPort != NULLCHAR)
1201               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1202                              appData.icsCommPort);
1203             else
1204               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1205                         appData.icsHost, appData.icsPort);
1206
1207             if( (len > MSG_SIZ) && appData.debugMode )
1208               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1209
1210             DisplayFatalError(buf, err, 1);
1211             return;
1212         }
1213         SetICSMode();
1214         telnetISR =
1215           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1216         fromUserISR =
1217           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1218         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1219             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1220     } else if (appData.noChessProgram) {
1221         SetNCPMode();
1222     } else {
1223         SetGNUMode();
1224     }
1225
1226     if (*appData.cmailGameName != NULLCHAR) {
1227         SetCmailMode();
1228         OpenLoopback(&cmailPR);
1229         cmailISR =
1230           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1231     }
1232
1233     ThawUI();
1234     DisplayMessage("", "");
1235     if (StrCaseCmp(appData.initialMode, "") == 0) {
1236       initialMode = BeginningOfGame;
1237     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1238       initialMode = TwoMachinesPlay;
1239     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1240       initialMode = AnalyzeFile;
1241     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1242       initialMode = AnalyzeMode;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1244       initialMode = MachinePlaysWhite;
1245     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1246       initialMode = MachinePlaysBlack;
1247     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1248       initialMode = EditGame;
1249     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1250       initialMode = EditPosition;
1251     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1252       initialMode = Training;
1253     } else {
1254       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1255       if( (len > MSG_SIZ) && appData.debugMode )
1256         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1257
1258       DisplayFatalError(buf, 0, 2);
1259       return;
1260     }
1261
1262     if (appData.matchMode) {
1263         /* Set up machine vs. machine match */
1264         if (appData.noChessProgram) {
1265             DisplayFatalError(_("Can't have a match with no chess programs"),
1266                               0, 2);
1267             return;
1268         }
1269         matchMode = TRUE;
1270         matchGame = 1;
1271         if (*appData.loadGameFile != NULLCHAR) {
1272             int index = appData.loadGameIndex; // [HGM] autoinc
1273             if(index<0) lastIndex = index = 1;
1274             if (!LoadGameFromFile(appData.loadGameFile,
1275                                   index,
1276                                   appData.loadGameFile, FALSE)) {
1277                 DisplayFatalError(_("Bad game file"), 0, 1);
1278                 return;
1279             }
1280         } else if (*appData.loadPositionFile != NULLCHAR) {
1281             int index = appData.loadPositionIndex; // [HGM] autoinc
1282             if(index<0) lastIndex = index = 1;
1283             if (!LoadPositionFromFile(appData.loadPositionFile,
1284                                       index,
1285                                       appData.loadPositionFile)) {
1286                 DisplayFatalError(_("Bad position file"), 0, 1);
1287                 return;
1288             }
1289         }
1290         TwoMachinesEvent();
1291     } else if (*appData.cmailGameName != NULLCHAR) {
1292         /* Set up cmail mode */
1293         ReloadCmailMsgEvent(TRUE);
1294     } else {
1295         /* Set up other modes */
1296         if (initialMode == AnalyzeFile) {
1297           if (*appData.loadGameFile == NULLCHAR) {
1298             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1299             return;
1300           }
1301         }
1302         if (*appData.loadGameFile != NULLCHAR) {
1303             (void) LoadGameFromFile(appData.loadGameFile,
1304                                     appData.loadGameIndex,
1305                                     appData.loadGameFile, TRUE);
1306         } else if (*appData.loadPositionFile != NULLCHAR) {
1307             (void) LoadPositionFromFile(appData.loadPositionFile,
1308                                         appData.loadPositionIndex,
1309                                         appData.loadPositionFile);
1310             /* [HGM] try to make self-starting even after FEN load */
1311             /* to allow automatic setup of fairy variants with wtm */
1312             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1313                 gameMode = BeginningOfGame;
1314                 setboardSpoiledMachineBlack = 1;
1315             }
1316             /* [HGM] loadPos: make that every new game uses the setup */
1317             /* from file as long as we do not switch variant          */
1318             if(!blackPlaysFirst) {
1319                 startedFromPositionFile = TRUE;
1320                 CopyBoard(filePosition, boards[0]);
1321             }
1322         }
1323         if (initialMode == AnalyzeMode) {
1324           if (appData.noChessProgram) {
1325             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1326             return;
1327           }
1328           if (appData.icsActive) {
1329             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1330             return;
1331           }
1332           AnalyzeModeEvent();
1333         } else if (initialMode == AnalyzeFile) {
1334           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1335           ShowThinkingEvent();
1336           AnalyzeFileEvent();
1337           AnalysisPeriodicEvent(1);
1338         } else if (initialMode == MachinePlaysWhite) {
1339           if (appData.noChessProgram) {
1340             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1341                               0, 2);
1342             return;
1343           }
1344           if (appData.icsActive) {
1345             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1346                               0, 2);
1347             return;
1348           }
1349           MachineWhiteEvent();
1350         } else if (initialMode == MachinePlaysBlack) {
1351           if (appData.noChessProgram) {
1352             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1353                               0, 2);
1354             return;
1355           }
1356           if (appData.icsActive) {
1357             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1358                               0, 2);
1359             return;
1360           }
1361           MachineBlackEvent();
1362         } else if (initialMode == TwoMachinesPlay) {
1363           if (appData.noChessProgram) {
1364             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1365                               0, 2);
1366             return;
1367           }
1368           if (appData.icsActive) {
1369             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1370                               0, 2);
1371             return;
1372           }
1373           TwoMachinesEvent();
1374         } else if (initialMode == EditGame) {
1375           EditGameEvent();
1376         } else if (initialMode == EditPosition) {
1377           EditPositionEvent();
1378         } else if (initialMode == Training) {
1379           if (*appData.loadGameFile == NULLCHAR) {
1380             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1381             return;
1382           }
1383           TrainingEvent();
1384         }
1385     }
1386 }
1387
1388 /*
1389  * Establish will establish a contact to a remote host.port.
1390  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1391  *  used to talk to the host.
1392  * Returns 0 if okay, error code if not.
1393  */
1394 int
1395 establish()
1396 {
1397     char buf[MSG_SIZ];
1398
1399     if (*appData.icsCommPort != NULLCHAR) {
1400         /* Talk to the host through a serial comm port */
1401         return OpenCommPort(appData.icsCommPort, &icsPR);
1402
1403     } else if (*appData.gateway != NULLCHAR) {
1404         if (*appData.remoteShell == NULLCHAR) {
1405             /* Use the rcmd protocol to run telnet program on a gateway host */
1406             snprintf(buf, sizeof(buf), "%s %s %s",
1407                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1408             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1409
1410         } else {
1411             /* Use the rsh program to run telnet program on a gateway host */
1412             if (*appData.remoteUser == NULLCHAR) {
1413                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1414                         appData.gateway, appData.telnetProgram,
1415                         appData.icsHost, appData.icsPort);
1416             } else {
1417                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1418                         appData.remoteShell, appData.gateway,
1419                         appData.remoteUser, appData.telnetProgram,
1420                         appData.icsHost, appData.icsPort);
1421             }
1422             return StartChildProcess(buf, "", &icsPR);
1423
1424         }
1425     } else if (appData.useTelnet) {
1426         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1427
1428     } else {
1429         /* TCP socket interface differs somewhat between
1430            Unix and NT; handle details in the front end.
1431            */
1432         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1433     }
1434 }
1435
1436 void EscapeExpand(char *p, char *q)
1437 {       // [HGM] initstring: routine to shape up string arguments
1438         while(*p++ = *q++) if(p[-1] == '\\')
1439             switch(*q++) {
1440                 case 'n': p[-1] = '\n'; break;
1441                 case 'r': p[-1] = '\r'; break;
1442                 case 't': p[-1] = '\t'; break;
1443                 case '\\': p[-1] = '\\'; break;
1444                 case 0: *p = 0; return;
1445                 default: p[-1] = q[-1]; break;
1446             }
1447 }
1448
1449 void
1450 show_bytes(fp, buf, count)
1451      FILE *fp;
1452      char *buf;
1453      int count;
1454 {
1455     while (count--) {
1456         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1457             fprintf(fp, "\\%03o", *buf & 0xff);
1458         } else {
1459             putc(*buf, fp);
1460         }
1461         buf++;
1462     }
1463     fflush(fp);
1464 }
1465
1466 /* Returns an errno value */
1467 int
1468 OutputMaybeTelnet(pr, message, count, outError)
1469      ProcRef pr;
1470      char *message;
1471      int count;
1472      int *outError;
1473 {
1474     char buf[8192], *p, *q, *buflim;
1475     int left, newcount, outcount;
1476
1477     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1478         *appData.gateway != NULLCHAR) {
1479         if (appData.debugMode) {
1480             fprintf(debugFP, ">ICS: ");
1481             show_bytes(debugFP, message, count);
1482             fprintf(debugFP, "\n");
1483         }
1484         return OutputToProcess(pr, message, count, outError);
1485     }
1486
1487     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1488     p = message;
1489     q = buf;
1490     left = count;
1491     newcount = 0;
1492     while (left) {
1493         if (q >= buflim) {
1494             if (appData.debugMode) {
1495                 fprintf(debugFP, ">ICS: ");
1496                 show_bytes(debugFP, buf, newcount);
1497                 fprintf(debugFP, "\n");
1498             }
1499             outcount = OutputToProcess(pr, buf, newcount, outError);
1500             if (outcount < newcount) return -1; /* to be sure */
1501             q = buf;
1502             newcount = 0;
1503         }
1504         if (*p == '\n') {
1505             *q++ = '\r';
1506             newcount++;
1507         } else if (((unsigned char) *p) == TN_IAC) {
1508             *q++ = (char) TN_IAC;
1509             newcount ++;
1510         }
1511         *q++ = *p++;
1512         newcount++;
1513         left--;
1514     }
1515     if (appData.debugMode) {
1516         fprintf(debugFP, ">ICS: ");
1517         show_bytes(debugFP, buf, newcount);
1518         fprintf(debugFP, "\n");
1519     }
1520     outcount = OutputToProcess(pr, buf, newcount, outError);
1521     if (outcount < newcount) return -1; /* to be sure */
1522     return count;
1523 }
1524
1525 void
1526 read_from_player(isr, closure, message, count, error)
1527      InputSourceRef isr;
1528      VOIDSTAR closure;
1529      char *message;
1530      int count;
1531      int error;
1532 {
1533     int outError, outCount;
1534     static int gotEof = 0;
1535
1536     /* Pass data read from player on to ICS */
1537     if (count > 0) {
1538         gotEof = 0;
1539         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1540         if (outCount < count) {
1541             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1542         }
1543     } else if (count < 0) {
1544         RemoveInputSource(isr);
1545         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1546     } else if (gotEof++ > 0) {
1547         RemoveInputSource(isr);
1548         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1549     }
1550 }
1551
1552 void
1553 KeepAlive()
1554 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1555     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1556     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1557     SendToICS("date\n");
1558     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1559 }
1560
1561 /* added routine for printf style output to ics */
1562 void ics_printf(char *format, ...)
1563 {
1564     char buffer[MSG_SIZ];
1565     va_list args;
1566
1567     va_start(args, format);
1568     vsnprintf(buffer, sizeof(buffer), format, args);
1569     buffer[sizeof(buffer)-1] = '\0';
1570     SendToICS(buffer);
1571     va_end(args);
1572 }
1573
1574 void
1575 SendToICS(s)
1576      char *s;
1577 {
1578     int count, outCount, outError;
1579
1580     if (icsPR == NULL) return;
1581
1582     count = strlen(s);
1583     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1584     if (outCount < count) {
1585         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1586     }
1587 }
1588
1589 /* This is used for sending logon scripts to the ICS. Sending
1590    without a delay causes problems when using timestamp on ICC
1591    (at least on my machine). */
1592 void
1593 SendToICSDelayed(s,msdelay)
1594      char *s;
1595      long msdelay;
1596 {
1597     int count, outCount, outError;
1598
1599     if (icsPR == NULL) return;
1600
1601     count = strlen(s);
1602     if (appData.debugMode) {
1603         fprintf(debugFP, ">ICS: ");
1604         show_bytes(debugFP, s, count);
1605         fprintf(debugFP, "\n");
1606     }
1607     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1608                                       msdelay);
1609     if (outCount < count) {
1610         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1611     }
1612 }
1613
1614
1615 /* Remove all highlighting escape sequences in s
1616    Also deletes any suffix starting with '('
1617    */
1618 char *
1619 StripHighlightAndTitle(s)
1620      char *s;
1621 {
1622     static char retbuf[MSG_SIZ];
1623     char *p = retbuf;
1624
1625     while (*s != NULLCHAR) {
1626         while (*s == '\033') {
1627             while (*s != NULLCHAR && !isalpha(*s)) s++;
1628             if (*s != NULLCHAR) s++;
1629         }
1630         while (*s != NULLCHAR && *s != '\033') {
1631             if (*s == '(' || *s == '[') {
1632                 *p = NULLCHAR;
1633                 return retbuf;
1634             }
1635             *p++ = *s++;
1636         }
1637     }
1638     *p = NULLCHAR;
1639     return retbuf;
1640 }
1641
1642 /* Remove all highlighting escape sequences in s */
1643 char *
1644 StripHighlight(s)
1645      char *s;
1646 {
1647     static char retbuf[MSG_SIZ];
1648     char *p = retbuf;
1649
1650     while (*s != NULLCHAR) {
1651         while (*s == '\033') {
1652             while (*s != NULLCHAR && !isalpha(*s)) s++;
1653             if (*s != NULLCHAR) s++;
1654         }
1655         while (*s != NULLCHAR && *s != '\033') {
1656             *p++ = *s++;
1657         }
1658     }
1659     *p = NULLCHAR;
1660     return retbuf;
1661 }
1662
1663 char *variantNames[] = VARIANT_NAMES;
1664 char *
1665 VariantName(v)
1666      VariantClass v;
1667 {
1668     return variantNames[v];
1669 }
1670
1671
1672 /* Identify a variant from the strings the chess servers use or the
1673    PGN Variant tag names we use. */
1674 VariantClass
1675 StringToVariant(e)
1676      char *e;
1677 {
1678     char *p;
1679     int wnum = -1;
1680     VariantClass v = VariantNormal;
1681     int i, found = FALSE;
1682     char buf[MSG_SIZ];
1683     int len;
1684
1685     if (!e) return v;
1686
1687     /* [HGM] skip over optional board-size prefixes */
1688     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1689         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1690         while( *e++ != '_');
1691     }
1692
1693     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1694         v = VariantNormal;
1695         found = TRUE;
1696     } else
1697     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1698       if (StrCaseStr(e, variantNames[i])) {
1699         v = (VariantClass) i;
1700         found = TRUE;
1701         break;
1702       }
1703     }
1704
1705     if (!found) {
1706       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1707           || StrCaseStr(e, "wild/fr")
1708           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1709         v = VariantFischeRandom;
1710       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1711                  (i = 1, p = StrCaseStr(e, "w"))) {
1712         p += i;
1713         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1714         if (isdigit(*p)) {
1715           wnum = atoi(p);
1716         } else {
1717           wnum = -1;
1718         }
1719         switch (wnum) {
1720         case 0: /* FICS only, actually */
1721         case 1:
1722           /* Castling legal even if K starts on d-file */
1723           v = VariantWildCastle;
1724           break;
1725         case 2:
1726         case 3:
1727         case 4:
1728           /* Castling illegal even if K & R happen to start in
1729              normal positions. */
1730           v = VariantNoCastle;
1731           break;
1732         case 5:
1733         case 7:
1734         case 8:
1735         case 10:
1736         case 11:
1737         case 12:
1738         case 13:
1739         case 14:
1740         case 15:
1741         case 18:
1742         case 19:
1743           /* Castling legal iff K & R start in normal positions */
1744           v = VariantNormal;
1745           break;
1746         case 6:
1747         case 20:
1748         case 21:
1749           /* Special wilds for position setup; unclear what to do here */
1750           v = VariantLoadable;
1751           break;
1752         case 9:
1753           /* Bizarre ICC game */
1754           v = VariantTwoKings;
1755           break;
1756         case 16:
1757           v = VariantKriegspiel;
1758           break;
1759         case 17:
1760           v = VariantLosers;
1761           break;
1762         case 22:
1763           v = VariantFischeRandom;
1764           break;
1765         case 23:
1766           v = VariantCrazyhouse;
1767           break;
1768         case 24:
1769           v = VariantBughouse;
1770           break;
1771         case 25:
1772           v = Variant3Check;
1773           break;
1774         case 26:
1775           /* Not quite the same as FICS suicide! */
1776           v = VariantGiveaway;
1777           break;
1778         case 27:
1779           v = VariantAtomic;
1780           break;
1781         case 28:
1782           v = VariantShatranj;
1783           break;
1784
1785         /* Temporary names for future ICC types.  The name *will* change in
1786            the next xboard/WinBoard release after ICC defines it. */
1787         case 29:
1788           v = Variant29;
1789           break;
1790         case 30:
1791           v = Variant30;
1792           break;
1793         case 31:
1794           v = Variant31;
1795           break;
1796         case 32:
1797           v = Variant32;
1798           break;
1799         case 33:
1800           v = Variant33;
1801           break;
1802         case 34:
1803           v = Variant34;
1804           break;
1805         case 35:
1806           v = Variant35;
1807           break;
1808         case 36:
1809           v = Variant36;
1810           break;
1811         case 37:
1812           v = VariantShogi;
1813           break;
1814         case 38:
1815           v = VariantXiangqi;
1816           break;
1817         case 39:
1818           v = VariantCourier;
1819           break;
1820         case 40:
1821           v = VariantGothic;
1822           break;
1823         case 41:
1824           v = VariantCapablanca;
1825           break;
1826         case 42:
1827           v = VariantKnightmate;
1828           break;
1829         case 43:
1830           v = VariantFairy;
1831           break;
1832         case 44:
1833           v = VariantCylinder;
1834           break;
1835         case 45:
1836           v = VariantFalcon;
1837           break;
1838         case 46:
1839           v = VariantCapaRandom;
1840           break;
1841         case 47:
1842           v = VariantBerolina;
1843           break;
1844         case 48:
1845           v = VariantJanus;
1846           break;
1847         case 49:
1848           v = VariantSuper;
1849           break;
1850         case 50:
1851           v = VariantGreat;
1852           break;
1853         case -1:
1854           /* Found "wild" or "w" in the string but no number;
1855              must assume it's normal chess. */
1856           v = VariantNormal;
1857           break;
1858         default:
1859           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1860           if( (len > MSG_SIZ) && appData.debugMode )
1861             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1862
1863           DisplayError(buf, 0);
1864           v = VariantUnknown;
1865           break;
1866         }
1867       }
1868     }
1869     if (appData.debugMode) {
1870       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1871               e, wnum, VariantName(v));
1872     }
1873     return v;
1874 }
1875
1876 static int leftover_start = 0, leftover_len = 0;
1877 char star_match[STAR_MATCH_N][MSG_SIZ];
1878
1879 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1880    advance *index beyond it, and set leftover_start to the new value of
1881    *index; else return FALSE.  If pattern contains the character '*', it
1882    matches any sequence of characters not containing '\r', '\n', or the
1883    character following the '*' (if any), and the matched sequence(s) are
1884    copied into star_match.
1885    */
1886 int
1887 looking_at(buf, index, pattern)
1888      char *buf;
1889      int *index;
1890      char *pattern;
1891 {
1892     char *bufp = &buf[*index], *patternp = pattern;
1893     int star_count = 0;
1894     char *matchp = star_match[0];
1895
1896     for (;;) {
1897         if (*patternp == NULLCHAR) {
1898             *index = leftover_start = bufp - buf;
1899             *matchp = NULLCHAR;
1900             return TRUE;
1901         }
1902         if (*bufp == NULLCHAR) return FALSE;
1903         if (*patternp == '*') {
1904             if (*bufp == *(patternp + 1)) {
1905                 *matchp = NULLCHAR;
1906                 matchp = star_match[++star_count];
1907                 patternp += 2;
1908                 bufp++;
1909                 continue;
1910             } else if (*bufp == '\n' || *bufp == '\r') {
1911                 patternp++;
1912                 if (*patternp == NULLCHAR)
1913                   continue;
1914                 else
1915                   return FALSE;
1916             } else {
1917                 *matchp++ = *bufp++;
1918                 continue;
1919             }
1920         }
1921         if (*patternp != *bufp) return FALSE;
1922         patternp++;
1923         bufp++;
1924     }
1925 }
1926
1927 void
1928 SendToPlayer(data, length)
1929      char *data;
1930      int length;
1931 {
1932     int error, outCount;
1933     outCount = OutputToProcess(NoProc, data, length, &error);
1934     if (outCount < length) {
1935         DisplayFatalError(_("Error writing to display"), error, 1);
1936     }
1937 }
1938
1939 void
1940 PackHolding(packed, holding)
1941      char packed[];
1942      char *holding;
1943 {
1944     char *p = holding;
1945     char *q = packed;
1946     int runlength = 0;
1947     int curr = 9999;
1948     do {
1949         if (*p == curr) {
1950             runlength++;
1951         } else {
1952             switch (runlength) {
1953               case 0:
1954                 break;
1955               case 1:
1956                 *q++ = curr;
1957                 break;
1958               case 2:
1959                 *q++ = curr;
1960                 *q++ = curr;
1961                 break;
1962               default:
1963                 sprintf(q, "%d", runlength);
1964                 while (*q) q++;
1965                 *q++ = curr;
1966                 break;
1967             }
1968             runlength = 1;
1969             curr = *p;
1970         }
1971     } while (*p++);
1972     *q = NULLCHAR;
1973 }
1974
1975 /* Telnet protocol requests from the front end */
1976 void
1977 TelnetRequest(ddww, option)
1978      unsigned char ddww, option;
1979 {
1980     unsigned char msg[3];
1981     int outCount, outError;
1982
1983     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1984
1985     if (appData.debugMode) {
1986         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1987         switch (ddww) {
1988           case TN_DO:
1989             ddwwStr = "DO";
1990             break;
1991           case TN_DONT:
1992             ddwwStr = "DONT";
1993             break;
1994           case TN_WILL:
1995             ddwwStr = "WILL";
1996             break;
1997           case TN_WONT:
1998             ddwwStr = "WONT";
1999             break;
2000           default:
2001             ddwwStr = buf1;
2002             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2003             break;
2004         }
2005         switch (option) {
2006           case TN_ECHO:
2007             optionStr = "ECHO";
2008             break;
2009           default:
2010             optionStr = buf2;
2011             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2012             break;
2013         }
2014         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2015     }
2016     msg[0] = TN_IAC;
2017     msg[1] = ddww;
2018     msg[2] = option;
2019     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2020     if (outCount < 3) {
2021         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2022     }
2023 }
2024
2025 void
2026 DoEcho()
2027 {
2028     if (!appData.icsActive) return;
2029     TelnetRequest(TN_DO, TN_ECHO);
2030 }
2031
2032 void
2033 DontEcho()
2034 {
2035     if (!appData.icsActive) return;
2036     TelnetRequest(TN_DONT, TN_ECHO);
2037 }
2038
2039 void
2040 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2041 {
2042     /* put the holdings sent to us by the server on the board holdings area */
2043     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2044     char p;
2045     ChessSquare piece;
2046
2047     if(gameInfo.holdingsWidth < 2)  return;
2048     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2049         return; // prevent overwriting by pre-board holdings
2050
2051     if( (int)lowestPiece >= BlackPawn ) {
2052         holdingsColumn = 0;
2053         countsColumn = 1;
2054         holdingsStartRow = BOARD_HEIGHT-1;
2055         direction = -1;
2056     } else {
2057         holdingsColumn = BOARD_WIDTH-1;
2058         countsColumn = BOARD_WIDTH-2;
2059         holdingsStartRow = 0;
2060         direction = 1;
2061     }
2062
2063     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2064         board[i][holdingsColumn] = EmptySquare;
2065         board[i][countsColumn]   = (ChessSquare) 0;
2066     }
2067     while( (p=*holdings++) != NULLCHAR ) {
2068         piece = CharToPiece( ToUpper(p) );
2069         if(piece == EmptySquare) continue;
2070         /*j = (int) piece - (int) WhitePawn;*/
2071         j = PieceToNumber(piece);
2072         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2073         if(j < 0) continue;               /* should not happen */
2074         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2075         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2076         board[holdingsStartRow+j*direction][countsColumn]++;
2077     }
2078 }
2079
2080
2081 void
2082 VariantSwitch(Board board, VariantClass newVariant)
2083 {
2084    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2085    static Board oldBoard;
2086
2087    startedFromPositionFile = FALSE;
2088    if(gameInfo.variant == newVariant) return;
2089
2090    /* [HGM] This routine is called each time an assignment is made to
2091     * gameInfo.variant during a game, to make sure the board sizes
2092     * are set to match the new variant. If that means adding or deleting
2093     * holdings, we shift the playing board accordingly
2094     * This kludge is needed because in ICS observe mode, we get boards
2095     * of an ongoing game without knowing the variant, and learn about the
2096     * latter only later. This can be because of the move list we requested,
2097     * in which case the game history is refilled from the beginning anyway,
2098     * but also when receiving holdings of a crazyhouse game. In the latter
2099     * case we want to add those holdings to the already received position.
2100     */
2101
2102
2103    if (appData.debugMode) {
2104      fprintf(debugFP, "Switch board from %s to %s\n",
2105              VariantName(gameInfo.variant), VariantName(newVariant));
2106      setbuf(debugFP, NULL);
2107    }
2108    shuffleOpenings = 0;       /* [HGM] shuffle */
2109    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2110    switch(newVariant)
2111      {
2112      case VariantShogi:
2113        newWidth = 9;  newHeight = 9;
2114        gameInfo.holdingsSize = 7;
2115      case VariantBughouse:
2116      case VariantCrazyhouse:
2117        newHoldingsWidth = 2; break;
2118      case VariantGreat:
2119        newWidth = 10;
2120      case VariantSuper:
2121        newHoldingsWidth = 2;
2122        gameInfo.holdingsSize = 8;
2123        break;
2124      case VariantGothic:
2125      case VariantCapablanca:
2126      case VariantCapaRandom:
2127        newWidth = 10;
2128      default:
2129        newHoldingsWidth = gameInfo.holdingsSize = 0;
2130      };
2131
2132    if(newWidth  != gameInfo.boardWidth  ||
2133       newHeight != gameInfo.boardHeight ||
2134       newHoldingsWidth != gameInfo.holdingsWidth ) {
2135
2136      /* shift position to new playing area, if needed */
2137      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2138        for(i=0; i<BOARD_HEIGHT; i++)
2139          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2140            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2141              board[i][j];
2142        for(i=0; i<newHeight; i++) {
2143          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2144          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2145        }
2146      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2147        for(i=0; i<BOARD_HEIGHT; i++)
2148          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2149            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2150              board[i][j];
2151      }
2152      gameInfo.boardWidth  = newWidth;
2153      gameInfo.boardHeight = newHeight;
2154      gameInfo.holdingsWidth = newHoldingsWidth;
2155      gameInfo.variant = newVariant;
2156      InitDrawingSizes(-2, 0);
2157    } else gameInfo.variant = newVariant;
2158    CopyBoard(oldBoard, board);   // remember correctly formatted board
2159      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2160    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2161 }
2162
2163 static int loggedOn = FALSE;
2164
2165 /*-- Game start info cache: --*/
2166 int gs_gamenum;
2167 char gs_kind[MSG_SIZ];
2168 static char player1Name[128] = "";
2169 static char player2Name[128] = "";
2170 static char cont_seq[] = "\n\\   ";
2171 static int player1Rating = -1;
2172 static int player2Rating = -1;
2173 /*----------------------------*/
2174
2175 ColorClass curColor = ColorNormal;
2176 int suppressKibitz = 0;
2177
2178 // [HGM] seekgraph
2179 Boolean soughtPending = FALSE;
2180 Boolean seekGraphUp;
2181 #define MAX_SEEK_ADS 200
2182 #define SQUARE 0x80
2183 char *seekAdList[MAX_SEEK_ADS];
2184 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2185 float tcList[MAX_SEEK_ADS];
2186 char colorList[MAX_SEEK_ADS];
2187 int nrOfSeekAds = 0;
2188 int minRating = 1010, maxRating = 2800;
2189 int hMargin = 10, vMargin = 20, h, w;
2190 extern int squareSize, lineGap;
2191
2192 void
2193 PlotSeekAd(int i)
2194 {
2195         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2196         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2197         if(r < minRating+100 && r >=0 ) r = minRating+100;
2198         if(r > maxRating) r = maxRating;
2199         if(tc < 1.) tc = 1.;
2200         if(tc > 95.) tc = 95.;
2201         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2202         y = ((double)r - minRating)/(maxRating - minRating)
2203             * (h-vMargin-squareSize/8-1) + vMargin;
2204         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2205         if(strstr(seekAdList[i], " u ")) color = 1;
2206         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2207            !strstr(seekAdList[i], "bullet") &&
2208            !strstr(seekAdList[i], "blitz") &&
2209            !strstr(seekAdList[i], "standard") ) color = 2;
2210         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2211         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2212 }
2213
2214 void
2215 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2216 {
2217         char buf[MSG_SIZ], *ext = "";
2218         VariantClass v = StringToVariant(type);
2219         if(strstr(type, "wild")) {
2220             ext = type + 4; // append wild number
2221             if(v == VariantFischeRandom) type = "chess960"; else
2222             if(v == VariantLoadable) type = "setup"; else
2223             type = VariantName(v);
2224         }
2225         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2226         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2227             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2228             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2229             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2230             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2231             seekNrList[nrOfSeekAds] = nr;
2232             zList[nrOfSeekAds] = 0;
2233             seekAdList[nrOfSeekAds++] = StrSave(buf);
2234             if(plot) PlotSeekAd(nrOfSeekAds-1);
2235         }
2236 }
2237
2238 void
2239 EraseSeekDot(int i)
2240 {
2241     int x = xList[i], y = yList[i], d=squareSize/4, k;
2242     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2243     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2244     // now replot every dot that overlapped
2245     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2246         int xx = xList[k], yy = yList[k];
2247         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2248             DrawSeekDot(xx, yy, colorList[k]);
2249     }
2250 }
2251
2252 void
2253 RemoveSeekAd(int nr)
2254 {
2255         int i;
2256         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2257             EraseSeekDot(i);
2258             if(seekAdList[i]) free(seekAdList[i]);
2259             seekAdList[i] = seekAdList[--nrOfSeekAds];
2260             seekNrList[i] = seekNrList[nrOfSeekAds];
2261             ratingList[i] = ratingList[nrOfSeekAds];
2262             colorList[i]  = colorList[nrOfSeekAds];
2263             tcList[i] = tcList[nrOfSeekAds];
2264             xList[i]  = xList[nrOfSeekAds];
2265             yList[i]  = yList[nrOfSeekAds];
2266             zList[i]  = zList[nrOfSeekAds];
2267             seekAdList[nrOfSeekAds] = NULL;
2268             break;
2269         }
2270 }
2271
2272 Boolean
2273 MatchSoughtLine(char *line)
2274 {
2275     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2276     int nr, base, inc, u=0; char dummy;
2277
2278     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2279        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2280        (u=1) &&
2281        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2282         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2283         // match: compact and save the line
2284         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2285         return TRUE;
2286     }
2287     return FALSE;
2288 }
2289
2290 int
2291 DrawSeekGraph()
2292 {
2293     int i;
2294     if(!seekGraphUp) return FALSE;
2295     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2296     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2297
2298     DrawSeekBackground(0, 0, w, h);
2299     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2300     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2301     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2302         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2303         yy = h-1-yy;
2304         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2305         if(i%500 == 0) {
2306             char buf[MSG_SIZ];
2307             snprintf(buf, MSG_SIZ, "%d", i);
2308             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2309         }
2310     }
2311     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2312     for(i=1; i<100; i+=(i<10?1:5)) {
2313         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2314         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2315         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2316             char buf[MSG_SIZ];
2317             snprintf(buf, MSG_SIZ, "%d", i);
2318             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2319         }
2320     }
2321     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2322     return TRUE;
2323 }
2324
2325 int SeekGraphClick(ClickType click, int x, int y, int moving)
2326 {
2327     static int lastDown = 0, displayed = 0, lastSecond;
2328     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2329         if(click == Release || moving) return FALSE;
2330         nrOfSeekAds = 0;
2331         soughtPending = TRUE;
2332         SendToICS(ics_prefix);
2333         SendToICS("sought\n"); // should this be "sought all"?
2334     } else { // issue challenge based on clicked ad
2335         int dist = 10000; int i, closest = 0, second = 0;
2336         for(i=0; i<nrOfSeekAds; i++) {
2337             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2338             if(d < dist) { dist = d; closest = i; }
2339             second += (d - zList[i] < 120); // count in-range ads
2340             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2341         }
2342         if(dist < 120) {
2343             char buf[MSG_SIZ];
2344             second = (second > 1);
2345             if(displayed != closest || second != lastSecond) {
2346                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2347                 lastSecond = second; displayed = closest;
2348             }
2349             if(click == Press) {
2350                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2351                 lastDown = closest;
2352                 return TRUE;
2353             } // on press 'hit', only show info
2354             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2355             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2356             SendToICS(ics_prefix);
2357             SendToICS(buf);
2358             return TRUE; // let incoming board of started game pop down the graph
2359         } else if(click == Release) { // release 'miss' is ignored
2360             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2361             if(moving == 2) { // right up-click
2362                 nrOfSeekAds = 0; // refresh graph
2363                 soughtPending = TRUE;
2364                 SendToICS(ics_prefix);
2365                 SendToICS("sought\n"); // should this be "sought all"?
2366             }
2367             return TRUE;
2368         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2369         // press miss or release hit 'pop down' seek graph
2370         seekGraphUp = FALSE;
2371         DrawPosition(TRUE, NULL);
2372     }
2373     return TRUE;
2374 }
2375
2376 void
2377 read_from_ics(isr, closure, data, count, error)
2378      InputSourceRef isr;
2379      VOIDSTAR closure;
2380      char *data;
2381      int count;
2382      int error;
2383 {
2384 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2385 #define STARTED_NONE 0
2386 #define STARTED_MOVES 1
2387 #define STARTED_BOARD 2
2388 #define STARTED_OBSERVE 3
2389 #define STARTED_HOLDINGS 4
2390 #define STARTED_CHATTER 5
2391 #define STARTED_COMMENT 6
2392 #define STARTED_MOVES_NOHIDE 7
2393
2394     static int started = STARTED_NONE;
2395     static char parse[20000];
2396     static int parse_pos = 0;
2397     static char buf[BUF_SIZE + 1];
2398     static int firstTime = TRUE, intfSet = FALSE;
2399     static ColorClass prevColor = ColorNormal;
2400     static int savingComment = FALSE;
2401     static int cmatch = 0; // continuation sequence match
2402     char *bp;
2403     char str[MSG_SIZ];
2404     int i, oldi;
2405     int buf_len;
2406     int next_out;
2407     int tkind;
2408     int backup;    /* [DM] For zippy color lines */
2409     char *p;
2410     char talker[MSG_SIZ]; // [HGM] chat
2411     int channel;
2412
2413     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2414
2415     if (appData.debugMode) {
2416       if (!error) {
2417         fprintf(debugFP, "<ICS: ");
2418         show_bytes(debugFP, data, count);
2419         fprintf(debugFP, "\n");
2420       }
2421     }
2422
2423     if (appData.debugMode) { int f = forwardMostMove;
2424         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2425                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2426                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2427     }
2428     if (count > 0) {
2429         /* If last read ended with a partial line that we couldn't parse,
2430            prepend it to the new read and try again. */
2431         if (leftover_len > 0) {
2432             for (i=0; i<leftover_len; i++)
2433               buf[i] = buf[leftover_start + i];
2434         }
2435
2436     /* copy new characters into the buffer */
2437     bp = buf + leftover_len;
2438     buf_len=leftover_len;
2439     for (i=0; i<count; i++)
2440     {
2441         // ignore these
2442         if (data[i] == '\r')
2443             continue;
2444
2445         // join lines split by ICS?
2446         if (!appData.noJoin)
2447         {
2448             /*
2449                 Joining just consists of finding matches against the
2450                 continuation sequence, and discarding that sequence
2451                 if found instead of copying it.  So, until a match
2452                 fails, there's nothing to do since it might be the
2453                 complete sequence, and thus, something we don't want
2454                 copied.
2455             */
2456             if (data[i] == cont_seq[cmatch])
2457             {
2458                 cmatch++;
2459                 if (cmatch == strlen(cont_seq))
2460                 {
2461                     cmatch = 0; // complete match.  just reset the counter
2462
2463                     /*
2464                         it's possible for the ICS to not include the space
2465                         at the end of the last word, making our [correct]
2466                         join operation fuse two separate words.  the server
2467                         does this when the space occurs at the width setting.
2468                     */
2469                     if (!buf_len || buf[buf_len-1] != ' ')
2470                     {
2471                         *bp++ = ' ';
2472                         buf_len++;
2473                     }
2474                 }
2475                 continue;
2476             }
2477             else if (cmatch)
2478             {
2479                 /*
2480                     match failed, so we have to copy what matched before
2481                     falling through and copying this character.  In reality,
2482                     this will only ever be just the newline character, but
2483                     it doesn't hurt to be precise.
2484                 */
2485                 strncpy(bp, cont_seq, cmatch);
2486                 bp += cmatch;
2487                 buf_len += cmatch;
2488                 cmatch = 0;
2489             }
2490         }
2491
2492         // copy this char
2493         *bp++ = data[i];
2494         buf_len++;
2495     }
2496
2497         buf[buf_len] = NULLCHAR;
2498 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2499         next_out = 0;
2500         leftover_start = 0;
2501
2502         i = 0;
2503         while (i < buf_len) {
2504             /* Deal with part of the TELNET option negotiation
2505                protocol.  We refuse to do anything beyond the
2506                defaults, except that we allow the WILL ECHO option,
2507                which ICS uses to turn off password echoing when we are
2508                directly connected to it.  We reject this option
2509                if localLineEditing mode is on (always on in xboard)
2510                and we are talking to port 23, which might be a real
2511                telnet server that will try to keep WILL ECHO on permanently.
2512              */
2513             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2514                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2515                 unsigned char option;
2516                 oldi = i;
2517                 switch ((unsigned char) buf[++i]) {
2518                   case TN_WILL:
2519                     if (appData.debugMode)
2520                       fprintf(debugFP, "\n<WILL ");
2521                     switch (option = (unsigned char) buf[++i]) {
2522                       case TN_ECHO:
2523                         if (appData.debugMode)
2524                           fprintf(debugFP, "ECHO ");
2525                         /* Reply only if this is a change, according
2526                            to the protocol rules. */
2527                         if (remoteEchoOption) break;
2528                         if (appData.localLineEditing &&
2529                             atoi(appData.icsPort) == TN_PORT) {
2530                             TelnetRequest(TN_DONT, TN_ECHO);
2531                         } else {
2532                             EchoOff();
2533                             TelnetRequest(TN_DO, TN_ECHO);
2534                             remoteEchoOption = TRUE;
2535                         }
2536                         break;
2537                       default:
2538                         if (appData.debugMode)
2539                           fprintf(debugFP, "%d ", option);
2540                         /* Whatever this is, we don't want it. */
2541                         TelnetRequest(TN_DONT, option);
2542                         break;
2543                     }
2544                     break;
2545                   case TN_WONT:
2546                     if (appData.debugMode)
2547                       fprintf(debugFP, "\n<WONT ");
2548                     switch (option = (unsigned char) buf[++i]) {
2549                       case TN_ECHO:
2550                         if (appData.debugMode)
2551                           fprintf(debugFP, "ECHO ");
2552                         /* Reply only if this is a change, according
2553                            to the protocol rules. */
2554                         if (!remoteEchoOption) break;
2555                         EchoOn();
2556                         TelnetRequest(TN_DONT, TN_ECHO);
2557                         remoteEchoOption = FALSE;
2558                         break;
2559                       default:
2560                         if (appData.debugMode)
2561                           fprintf(debugFP, "%d ", (unsigned char) option);
2562                         /* Whatever this is, it must already be turned
2563                            off, because we never agree to turn on
2564                            anything non-default, so according to the
2565                            protocol rules, we don't reply. */
2566                         break;
2567                     }
2568                     break;
2569                   case TN_DO:
2570                     if (appData.debugMode)
2571                       fprintf(debugFP, "\n<DO ");
2572                     switch (option = (unsigned char) buf[++i]) {
2573                       default:
2574                         /* Whatever this is, we refuse to do it. */
2575                         if (appData.debugMode)
2576                           fprintf(debugFP, "%d ", option);
2577                         TelnetRequest(TN_WONT, option);
2578                         break;
2579                     }
2580                     break;
2581                   case TN_DONT:
2582                     if (appData.debugMode)
2583                       fprintf(debugFP, "\n<DONT ");
2584                     switch (option = (unsigned char) buf[++i]) {
2585                       default:
2586                         if (appData.debugMode)
2587                           fprintf(debugFP, "%d ", option);
2588                         /* Whatever this is, we are already not doing
2589                            it, because we never agree to do anything
2590                            non-default, so according to the protocol
2591                            rules, we don't reply. */
2592                         break;
2593                     }
2594                     break;
2595                   case TN_IAC:
2596                     if (appData.debugMode)
2597                       fprintf(debugFP, "\n<IAC ");
2598                     /* Doubled IAC; pass it through */
2599                     i--;
2600                     break;
2601                   default:
2602                     if (appData.debugMode)
2603                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2604                     /* Drop all other telnet commands on the floor */
2605                     break;
2606                 }
2607                 if (oldi > next_out)
2608                   SendToPlayer(&buf[next_out], oldi - next_out);
2609                 if (++i > next_out)
2610                   next_out = i;
2611                 continue;
2612             }
2613
2614             /* OK, this at least will *usually* work */
2615             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2616                 loggedOn = TRUE;
2617             }
2618
2619             if (loggedOn && !intfSet) {
2620                 if (ics_type == ICS_ICC) {
2621                   snprintf(str, MSG_SIZ,
2622                           "/set-quietly interface %s\n/set-quietly style 12\n",
2623                           programVersion);
2624                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2625                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2626                 } else if (ics_type == ICS_CHESSNET) {
2627                   snprintf(str, MSG_SIZ, "/style 12\n");
2628                 } else {
2629                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2630                   strcat(str, programVersion);
2631                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2632                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2633                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2634 #ifdef WIN32
2635                   strcat(str, "$iset nohighlight 1\n");
2636 #endif
2637                   strcat(str, "$iset lock 1\n$style 12\n");
2638                 }
2639                 SendToICS(str);
2640                 NotifyFrontendLogin();
2641                 intfSet = TRUE;
2642             }
2643
2644             if (started == STARTED_COMMENT) {
2645                 /* Accumulate characters in comment */
2646                 parse[parse_pos++] = buf[i];
2647                 if (buf[i] == '\n') {
2648                     parse[parse_pos] = NULLCHAR;
2649                     if(chattingPartner>=0) {
2650                         char mess[MSG_SIZ];
2651                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2652                         OutputChatMessage(chattingPartner, mess);
2653                         chattingPartner = -1;
2654                         next_out = i+1; // [HGM] suppress printing in ICS window
2655                     } else
2656                     if(!suppressKibitz) // [HGM] kibitz
2657                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2658                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2659                         int nrDigit = 0, nrAlph = 0, j;
2660                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2661                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2662                         parse[parse_pos] = NULLCHAR;
2663                         // try to be smart: if it does not look like search info, it should go to
2664                         // ICS interaction window after all, not to engine-output window.
2665                         for(j=0; j<parse_pos; j++) { // count letters and digits
2666                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2667                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2668                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2669                         }
2670                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2671                             int depth=0; float score;
2672                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2673                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2674                                 pvInfoList[forwardMostMove-1].depth = depth;
2675                                 pvInfoList[forwardMostMove-1].score = 100*score;
2676                             }
2677                             OutputKibitz(suppressKibitz, parse);
2678                         } else {
2679                             char tmp[MSG_SIZ];
2680                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2681                             SendToPlayer(tmp, strlen(tmp));
2682                         }
2683                         next_out = i+1; // [HGM] suppress printing in ICS window
2684                     }
2685                     started = STARTED_NONE;
2686                 } else {
2687                     /* Don't match patterns against characters in comment */
2688                     i++;
2689                     continue;
2690                 }
2691             }
2692             if (started == STARTED_CHATTER) {
2693                 if (buf[i] != '\n') {
2694                     /* Don't match patterns against characters in chatter */
2695                     i++;
2696                     continue;
2697                 }
2698                 started = STARTED_NONE;
2699                 if(suppressKibitz) next_out = i+1;
2700             }
2701
2702             /* Kludge to deal with rcmd protocol */
2703             if (firstTime && looking_at(buf, &i, "\001*")) {
2704                 DisplayFatalError(&buf[1], 0, 1);
2705                 continue;
2706             } else {
2707                 firstTime = FALSE;
2708             }
2709
2710             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2711                 ics_type = ICS_ICC;
2712                 ics_prefix = "/";
2713                 if (appData.debugMode)
2714                   fprintf(debugFP, "ics_type %d\n", ics_type);
2715                 continue;
2716             }
2717             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2718                 ics_type = ICS_FICS;
2719                 ics_prefix = "$";
2720                 if (appData.debugMode)
2721                   fprintf(debugFP, "ics_type %d\n", ics_type);
2722                 continue;
2723             }
2724             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2725                 ics_type = ICS_CHESSNET;
2726                 ics_prefix = "/";
2727                 if (appData.debugMode)
2728                   fprintf(debugFP, "ics_type %d\n", ics_type);
2729                 continue;
2730             }
2731
2732             if (!loggedOn &&
2733                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2734                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2735                  looking_at(buf, &i, "will be \"*\""))) {
2736               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2737               continue;
2738             }
2739
2740             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2741               char buf[MSG_SIZ];
2742               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2743               DisplayIcsInteractionTitle(buf);
2744               have_set_title = TRUE;
2745             }
2746
2747             /* skip finger notes */
2748             if (started == STARTED_NONE &&
2749                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2750                  (buf[i] == '1' && buf[i+1] == '0')) &&
2751                 buf[i+2] == ':' && buf[i+3] == ' ') {
2752               started = STARTED_CHATTER;
2753               i += 3;
2754               continue;
2755             }
2756
2757             oldi = i;
2758             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2759             if(appData.seekGraph) {
2760                 if(soughtPending && MatchSoughtLine(buf+i)) {
2761                     i = strstr(buf+i, "rated") - buf;
2762                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2763                     next_out = leftover_start = i;
2764                     started = STARTED_CHATTER;
2765                     suppressKibitz = TRUE;
2766                     continue;
2767                 }
2768                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2769                         && looking_at(buf, &i, "* ads displayed")) {
2770                     soughtPending = FALSE;
2771                     seekGraphUp = TRUE;
2772                     DrawSeekGraph();
2773                     continue;
2774                 }
2775                 if(appData.autoRefresh) {
2776                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2777                         int s = (ics_type == ICS_ICC); // ICC format differs
2778                         if(seekGraphUp)
2779                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2780                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2781                         looking_at(buf, &i, "*% "); // eat prompt
2782                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2783                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2784                         next_out = i; // suppress
2785                         continue;
2786                     }
2787                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2788                         char *p = star_match[0];
2789                         while(*p) {
2790                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2791                             while(*p && *p++ != ' '); // next
2792                         }
2793                         looking_at(buf, &i, "*% "); // eat prompt
2794                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2795                         next_out = i;
2796                         continue;
2797                     }
2798                 }
2799             }
2800
2801             /* skip formula vars */
2802             if (started == STARTED_NONE &&
2803                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2804               started = STARTED_CHATTER;
2805               i += 3;
2806               continue;
2807             }
2808
2809             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2810             if (appData.autoKibitz && started == STARTED_NONE &&
2811                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2812                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2813                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2814                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2815                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2816                         suppressKibitz = TRUE;
2817                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2818                         next_out = i;
2819                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2820                                 && (gameMode == IcsPlayingWhite)) ||
2821                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2822                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2823                             started = STARTED_CHATTER; // own kibitz we simply discard
2824                         else {
2825                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2826                             parse_pos = 0; parse[0] = NULLCHAR;
2827                             savingComment = TRUE;
2828                             suppressKibitz = gameMode != IcsObserving ? 2 :
2829                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2830                         }
2831                         continue;
2832                 } else
2833                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2834                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2835                          && atoi(star_match[0])) {
2836                     // suppress the acknowledgements of our own autoKibitz
2837                     char *p;
2838                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2839                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2840                     SendToPlayer(star_match[0], strlen(star_match[0]));
2841                     if(looking_at(buf, &i, "*% ")) // eat prompt
2842                         suppressKibitz = FALSE;
2843                     next_out = i;
2844                     continue;
2845                 }
2846             } // [HGM] kibitz: end of patch
2847
2848             // [HGM] chat: intercept tells by users for which we have an open chat window
2849             channel = -1;
2850             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2851                                            looking_at(buf, &i, "* whispers:") ||
2852                                            looking_at(buf, &i, "* kibitzes:") ||
2853                                            looking_at(buf, &i, "* shouts:") ||
2854                                            looking_at(buf, &i, "* c-shouts:") ||
2855                                            looking_at(buf, &i, "--> * ") ||
2856                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2858                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2859                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2860                 int p;
2861                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2862                 chattingPartner = -1;
2863
2864                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2865                 for(p=0; p<MAX_CHAT; p++) {
2866                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2867                     talker[0] = '['; strcat(talker, "] ");
2868                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2869                     chattingPartner = p; break;
2870                     }
2871                 } else
2872                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2873                 for(p=0; p<MAX_CHAT; p++) {
2874                     if(!strcmp("kibitzes", chatPartner[p])) {
2875                         talker[0] = '['; strcat(talker, "] ");
2876                         chattingPartner = p; break;
2877                     }
2878                 } else
2879                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2880                 for(p=0; p<MAX_CHAT; p++) {
2881                     if(!strcmp("whispers", chatPartner[p])) {
2882                         talker[0] = '['; strcat(talker, "] ");
2883                         chattingPartner = p; break;
2884                     }
2885                 } else
2886                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2887                   if(buf[i-8] == '-' && buf[i-3] == 't')
2888                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2889                     if(!strcmp("c-shouts", chatPartner[p])) {
2890                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2891                         chattingPartner = p; break;
2892                     }
2893                   }
2894                   if(chattingPartner < 0)
2895                   for(p=0; p<MAX_CHAT; p++) {
2896                     if(!strcmp("shouts", chatPartner[p])) {
2897                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2898                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2899                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2900                         chattingPartner = p; break;
2901                     }
2902                   }
2903                 }
2904                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2905                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2906                     talker[0] = 0; Colorize(ColorTell, FALSE);
2907                     chattingPartner = p; break;
2908                 }
2909                 if(chattingPartner<0) i = oldi; else {
2910                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2911                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2912                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2913                     started = STARTED_COMMENT;
2914                     parse_pos = 0; parse[0] = NULLCHAR;
2915                     savingComment = 3 + chattingPartner; // counts as TRUE
2916                     suppressKibitz = TRUE;
2917                     continue;
2918                 }
2919             } // [HGM] chat: end of patch
2920
2921             if (appData.zippyTalk || appData.zippyPlay) {
2922                 /* [DM] Backup address for color zippy lines */
2923                 backup = i;
2924 #if ZIPPY
2925                if (loggedOn == TRUE)
2926                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2927                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2928 #endif
2929             } // [DM] 'else { ' deleted
2930                 if (
2931                     /* Regular tells and says */
2932                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2933                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2934                     looking_at(buf, &i, "* says: ") ||
2935                     /* Don't color "message" or "messages" output */
2936                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2937                     looking_at(buf, &i, "*. * at *:*: ") ||
2938                     looking_at(buf, &i, "--* (*:*): ") ||
2939                     /* Message notifications (same color as tells) */
2940                     looking_at(buf, &i, "* has left a message ") ||
2941                     looking_at(buf, &i, "* just sent you a message:\n") ||
2942                     /* Whispers and kibitzes */
2943                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2944                     looking_at(buf, &i, "* kibitzes: ") ||
2945                     /* Channel tells */
2946                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2947
2948                   if (tkind == 1 && strchr(star_match[0], ':')) {
2949                       /* Avoid "tells you:" spoofs in channels */
2950                      tkind = 3;
2951                   }
2952                   if (star_match[0][0] == NULLCHAR ||
2953                       strchr(star_match[0], ' ') ||
2954                       (tkind == 3 && strchr(star_match[1], ' '))) {
2955                     /* Reject bogus matches */
2956                     i = oldi;
2957                   } else {
2958                     if (appData.colorize) {
2959                       if (oldi > next_out) {
2960                         SendToPlayer(&buf[next_out], oldi - next_out);
2961                         next_out = oldi;
2962                       }
2963                       switch (tkind) {
2964                       case 1:
2965                         Colorize(ColorTell, FALSE);
2966                         curColor = ColorTell;
2967                         break;
2968                       case 2:
2969                         Colorize(ColorKibitz, FALSE);
2970                         curColor = ColorKibitz;
2971                         break;
2972                       case 3:
2973                         p = strrchr(star_match[1], '(');
2974                         if (p == NULL) {
2975                           p = star_match[1];
2976                         } else {
2977                           p++;
2978                         }
2979                         if (atoi(p) == 1) {
2980                           Colorize(ColorChannel1, FALSE);
2981                           curColor = ColorChannel1;
2982                         } else {
2983                           Colorize(ColorChannel, FALSE);
2984                           curColor = ColorChannel;
2985                         }
2986                         break;
2987                       case 5:
2988                         curColor = ColorNormal;
2989                         break;
2990                       }
2991                     }
2992                     if (started == STARTED_NONE && appData.autoComment &&
2993                         (gameMode == IcsObserving ||
2994                          gameMode == IcsPlayingWhite ||
2995                          gameMode == IcsPlayingBlack)) {
2996                       parse_pos = i - oldi;
2997                       memcpy(parse, &buf[oldi], parse_pos);
2998                       parse[parse_pos] = NULLCHAR;
2999                       started = STARTED_COMMENT;
3000                       savingComment = TRUE;
3001                     } else {
3002                       started = STARTED_CHATTER;
3003                       savingComment = FALSE;
3004                     }
3005                     loggedOn = TRUE;
3006                     continue;
3007                   }
3008                 }
3009
3010                 if (looking_at(buf, &i, "* s-shouts: ") ||
3011                     looking_at(buf, &i, "* c-shouts: ")) {
3012                     if (appData.colorize) {
3013                         if (oldi > next_out) {
3014                             SendToPlayer(&buf[next_out], oldi - next_out);
3015                             next_out = oldi;
3016                         }
3017                         Colorize(ColorSShout, FALSE);
3018                         curColor = ColorSShout;
3019                     }
3020                     loggedOn = TRUE;
3021                     started = STARTED_CHATTER;
3022                     continue;
3023                 }
3024
3025                 if (looking_at(buf, &i, "--->")) {
3026                     loggedOn = TRUE;
3027                     continue;
3028                 }
3029
3030                 if (looking_at(buf, &i, "* shouts: ") ||
3031                     looking_at(buf, &i, "--> ")) {
3032                     if (appData.colorize) {
3033                         if (oldi > next_out) {
3034                             SendToPlayer(&buf[next_out], oldi - next_out);
3035                             next_out = oldi;
3036                         }
3037                         Colorize(ColorShout, FALSE);
3038                         curColor = ColorShout;
3039                     }
3040                     loggedOn = TRUE;
3041                     started = STARTED_CHATTER;
3042                     continue;
3043                 }
3044
3045                 if (looking_at( buf, &i, "Challenge:")) {
3046                     if (appData.colorize) {
3047                         if (oldi > next_out) {
3048                             SendToPlayer(&buf[next_out], oldi - next_out);
3049                             next_out = oldi;
3050                         }
3051                         Colorize(ColorChallenge, FALSE);
3052                         curColor = ColorChallenge;
3053                     }
3054                     loggedOn = TRUE;
3055                     continue;
3056                 }
3057
3058                 if (looking_at(buf, &i, "* offers you") ||
3059                     looking_at(buf, &i, "* offers to be") ||
3060                     looking_at(buf, &i, "* would like to") ||
3061                     looking_at(buf, &i, "* requests to") ||
3062                     looking_at(buf, &i, "Your opponent offers") ||
3063                     looking_at(buf, &i, "Your opponent requests")) {
3064
3065                     if (appData.colorize) {
3066                         if (oldi > next_out) {
3067                             SendToPlayer(&buf[next_out], oldi - next_out);
3068                             next_out = oldi;
3069                         }
3070                         Colorize(ColorRequest, FALSE);
3071                         curColor = ColorRequest;
3072                     }
3073                     continue;
3074                 }
3075
3076                 if (looking_at(buf, &i, "* (*) seeking")) {
3077                     if (appData.colorize) {
3078                         if (oldi > next_out) {
3079                             SendToPlayer(&buf[next_out], oldi - next_out);
3080                             next_out = oldi;
3081                         }
3082                         Colorize(ColorSeek, FALSE);
3083                         curColor = ColorSeek;
3084                     }
3085                     continue;
3086             }
3087
3088             if (looking_at(buf, &i, "\\   ")) {
3089                 if (prevColor != ColorNormal) {
3090                     if (oldi > next_out) {
3091                         SendToPlayer(&buf[next_out], oldi - next_out);
3092                         next_out = oldi;
3093                     }
3094                     Colorize(prevColor, TRUE);
3095                     curColor = prevColor;
3096                 }
3097                 if (savingComment) {
3098                     parse_pos = i - oldi;
3099                     memcpy(parse, &buf[oldi], parse_pos);
3100                     parse[parse_pos] = NULLCHAR;
3101                     started = STARTED_COMMENT;
3102                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3103                         chattingPartner = savingComment - 3; // kludge to remember the box
3104                 } else {
3105                     started = STARTED_CHATTER;
3106                 }
3107                 continue;
3108             }
3109
3110             if (looking_at(buf, &i, "Black Strength :") ||
3111                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3112                 looking_at(buf, &i, "<10>") ||
3113                 looking_at(buf, &i, "#@#")) {
3114                 /* Wrong board style */
3115                 loggedOn = TRUE;
3116                 SendToICS(ics_prefix);
3117                 SendToICS("set style 12\n");
3118                 SendToICS(ics_prefix);
3119                 SendToICS("refresh\n");
3120                 continue;
3121             }
3122
3123             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3124                 ICSInitScript();
3125                 have_sent_ICS_logon = 1;
3126                 continue;
3127             }
3128
3129             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3130                 (looking_at(buf, &i, "\n<12> ") ||
3131                  looking_at(buf, &i, "<12> "))) {
3132                 loggedOn = TRUE;
3133                 if (oldi > next_out) {
3134                     SendToPlayer(&buf[next_out], oldi - next_out);
3135                 }
3136                 next_out = i;
3137                 started = STARTED_BOARD;
3138                 parse_pos = 0;
3139                 continue;
3140             }
3141
3142             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3143                 looking_at(buf, &i, "<b1> ")) {
3144                 if (oldi > next_out) {
3145                     SendToPlayer(&buf[next_out], oldi - next_out);
3146                 }
3147                 next_out = i;
3148                 started = STARTED_HOLDINGS;
3149                 parse_pos = 0;
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3154                 loggedOn = TRUE;
3155                 /* Header for a move list -- first line */
3156
3157                 switch (ics_getting_history) {
3158                   case H_FALSE:
3159                     switch (gameMode) {
3160                       case IcsIdle:
3161                       case BeginningOfGame:
3162                         /* User typed "moves" or "oldmoves" while we
3163                            were idle.  Pretend we asked for these
3164                            moves and soak them up so user can step
3165                            through them and/or save them.
3166                            */
3167                         Reset(FALSE, TRUE);
3168                         gameMode = IcsObserving;
3169                         ModeHighlight();
3170                         ics_gamenum = -1;
3171                         ics_getting_history = H_GOT_UNREQ_HEADER;
3172                         break;
3173                       case EditGame: /*?*/
3174                       case EditPosition: /*?*/
3175                         /* Should above feature work in these modes too? */
3176                         /* For now it doesn't */
3177                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3178                         break;
3179                       default:
3180                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3181                         break;
3182                     }
3183                     break;
3184                   case H_REQUESTED:
3185                     /* Is this the right one? */
3186                     if (gameInfo.white && gameInfo.black &&
3187                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3188                         strcmp(gameInfo.black, star_match[2]) == 0) {
3189                         /* All is well */
3190                         ics_getting_history = H_GOT_REQ_HEADER;
3191                     }
3192                     break;
3193                   case H_GOT_REQ_HEADER:
3194                   case H_GOT_UNREQ_HEADER:
3195                   case H_GOT_UNWANTED_HEADER:
3196                   case H_GETTING_MOVES:
3197                     /* Should not happen */
3198                     DisplayError(_("Error gathering move list: two headers"), 0);
3199                     ics_getting_history = H_FALSE;
3200                     break;
3201                 }
3202
3203                 /* Save player ratings into gameInfo if needed */
3204                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3205                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3206                     (gameInfo.whiteRating == -1 ||
3207                      gameInfo.blackRating == -1)) {
3208
3209                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3210                     gameInfo.blackRating = string_to_rating(star_match[3]);
3211                     if (appData.debugMode)
3212                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3213                               gameInfo.whiteRating, gameInfo.blackRating);
3214                 }
3215                 continue;
3216             }
3217
3218             if (looking_at(buf, &i,
3219               "* * match, initial time: * minute*, increment: * second")) {
3220                 /* Header for a move list -- second line */
3221                 /* Initial board will follow if this is a wild game */
3222                 if (gameInfo.event != NULL) free(gameInfo.event);
3223                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3224                 gameInfo.event = StrSave(str);
3225                 /* [HGM] we switched variant. Translate boards if needed. */
3226                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3227                 continue;
3228             }
3229
3230             if (looking_at(buf, &i, "Move  ")) {
3231                 /* Beginning of a move list */
3232                 switch (ics_getting_history) {
3233                   case H_FALSE:
3234                     /* Normally should not happen */
3235                     /* Maybe user hit reset while we were parsing */
3236                     break;
3237                   case H_REQUESTED:
3238                     /* Happens if we are ignoring a move list that is not
3239                      * the one we just requested.  Common if the user
3240                      * tries to observe two games without turning off
3241                      * getMoveList */
3242                     break;
3243                   case H_GETTING_MOVES:
3244                     /* Should not happen */
3245                     DisplayError(_("Error gathering move list: nested"), 0);
3246                     ics_getting_history = H_FALSE;
3247                     break;
3248                   case H_GOT_REQ_HEADER:
3249                     ics_getting_history = H_GETTING_MOVES;
3250                     started = STARTED_MOVES;
3251                     parse_pos = 0;
3252                     if (oldi > next_out) {
3253                         SendToPlayer(&buf[next_out], oldi - next_out);
3254                     }
3255                     break;
3256                   case H_GOT_UNREQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES_NOHIDE;
3259                     parse_pos = 0;
3260                     break;
3261                   case H_GOT_UNWANTED_HEADER:
3262                     ics_getting_history = H_FALSE;
3263                     break;
3264                 }
3265                 continue;
3266             }
3267
3268             if (looking_at(buf, &i, "% ") ||
3269                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3270                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3271                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3272                     soughtPending = FALSE;
3273                     seekGraphUp = TRUE;
3274                     DrawSeekGraph();
3275                 }
3276                 if(suppressKibitz) next_out = i;
3277                 savingComment = FALSE;
3278                 suppressKibitz = 0;
3279                 switch (started) {
3280                   case STARTED_MOVES:
3281                   case STARTED_MOVES_NOHIDE:
3282                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3283                     parse[parse_pos + i - oldi] = NULLCHAR;
3284                     ParseGameHistory(parse);
3285 #if ZIPPY
3286                     if (appData.zippyPlay && first.initDone) {
3287                         FeedMovesToProgram(&first, forwardMostMove);
3288                         if (gameMode == IcsPlayingWhite) {
3289                             if (WhiteOnMove(forwardMostMove)) {
3290                                 if (first.sendTime) {
3291                                   if (first.useColors) {
3292                                     SendToProgram("black\n", &first);
3293                                   }
3294                                   SendTimeRemaining(&first, TRUE);
3295                                 }
3296                                 if (first.useColors) {
3297                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3298                                 }
3299                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3300                                 first.maybeThinking = TRUE;
3301                             } else {
3302                                 if (first.usePlayother) {
3303                                   if (first.sendTime) {
3304                                     SendTimeRemaining(&first, TRUE);
3305                                   }
3306                                   SendToProgram("playother\n", &first);
3307                                   firstMove = FALSE;
3308                                 } else {
3309                                   firstMove = TRUE;
3310                                 }
3311                             }
3312                         } else if (gameMode == IcsPlayingBlack) {
3313                             if (!WhiteOnMove(forwardMostMove)) {
3314                                 if (first.sendTime) {
3315                                   if (first.useColors) {
3316                                     SendToProgram("white\n", &first);
3317                                   }
3318                                   SendTimeRemaining(&first, FALSE);
3319                                 }
3320                                 if (first.useColors) {
3321                                   SendToProgram("black\n", &first);
3322                                 }
3323                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3324                                 first.maybeThinking = TRUE;
3325                             } else {
3326                                 if (first.usePlayother) {
3327                                   if (first.sendTime) {
3328                                     SendTimeRemaining(&first, FALSE);
3329                                   }
3330                                   SendToProgram("playother\n", &first);
3331                                   firstMove = FALSE;
3332                                 } else {
3333                                   firstMove = TRUE;
3334                                 }
3335                             }
3336                         }
3337                     }
3338 #endif
3339                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3340                         /* Moves came from oldmoves or moves command
3341                            while we weren't doing anything else.
3342                            */
3343                         currentMove = forwardMostMove;
3344                         ClearHighlights();/*!!could figure this out*/
3345                         flipView = appData.flipView;
3346                         DrawPosition(TRUE, boards[currentMove]);
3347                         DisplayBothClocks();
3348                         snprintf(str, MSG_SIZ, "%s vs. %s",
3349                                 gameInfo.white, gameInfo.black);
3350                         DisplayTitle(str);
3351                         gameMode = IcsIdle;
3352                     } else {
3353                         /* Moves were history of an active game */
3354                         if (gameInfo.resultDetails != NULL) {
3355                             free(gameInfo.resultDetails);
3356                             gameInfo.resultDetails = NULL;
3357                         }
3358                     }
3359                     HistorySet(parseList, backwardMostMove,
3360                                forwardMostMove, currentMove-1);
3361                     DisplayMove(currentMove - 1);
3362                     if (started == STARTED_MOVES) next_out = i;
3363                     started = STARTED_NONE;
3364                     ics_getting_history = H_FALSE;
3365                     break;
3366
3367                   case STARTED_OBSERVE:
3368                     started = STARTED_NONE;
3369                     SendToICS(ics_prefix);
3370                     SendToICS("refresh\n");
3371                     break;
3372
3373                   default:
3374                     break;
3375                 }
3376                 if(bookHit) { // [HGM] book: simulate book reply
3377                     static char bookMove[MSG_SIZ]; // a bit generous?
3378
3379                     programStats.nodes = programStats.depth = programStats.time =
3380                     programStats.score = programStats.got_only_move = 0;
3381                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3382
3383                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3384                     strcat(bookMove, bookHit);
3385                     HandleMachineMove(bookMove, &first);
3386                 }
3387                 continue;
3388             }
3389
3390             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3391                  started == STARTED_HOLDINGS ||
3392                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3393                 /* Accumulate characters in move list or board */
3394                 parse[parse_pos++] = buf[i];
3395             }
3396
3397             /* Start of game messages.  Mostly we detect start of game
3398                when the first board image arrives.  On some versions
3399                of the ICS, though, we need to do a "refresh" after starting
3400                to observe in order to get the current board right away. */
3401             if (looking_at(buf, &i, "Adding game * to observation list")) {
3402                 started = STARTED_OBSERVE;
3403                 continue;
3404             }
3405
3406             /* Handle auto-observe */
3407             if (appData.autoObserve &&
3408                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3409                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3410                 char *player;
3411                 /* Choose the player that was highlighted, if any. */
3412                 if (star_match[0][0] == '\033' ||
3413                     star_match[1][0] != '\033') {
3414                     player = star_match[0];
3415                 } else {
3416                     player = star_match[2];
3417                 }
3418                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3419                         ics_prefix, StripHighlightAndTitle(player));
3420                 SendToICS(str);
3421
3422                 /* Save ratings from notify string */
3423                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3424                 player1Rating = string_to_rating(star_match[1]);
3425                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3426                 player2Rating = string_to_rating(star_match[3]);
3427
3428                 if (appData.debugMode)
3429                   fprintf(debugFP,
3430                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3431                           player1Name, player1Rating,
3432                           player2Name, player2Rating);
3433
3434                 continue;
3435             }
3436
3437             /* Deal with automatic examine mode after a game,
3438                and with IcsObserving -> IcsExamining transition */
3439             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3440                 looking_at(buf, &i, "has made you an examiner of game *")) {
3441
3442                 int gamenum = atoi(star_match[0]);
3443                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3444                     gamenum == ics_gamenum) {
3445                     /* We were already playing or observing this game;
3446                        no need to refetch history */
3447                     gameMode = IcsExamining;
3448                     if (pausing) {
3449                         pauseExamForwardMostMove = forwardMostMove;
3450                     } else if (currentMove < forwardMostMove) {
3451                         ForwardInner(forwardMostMove);
3452                     }
3453                 } else {
3454                     /* I don't think this case really can happen */
3455                     SendToICS(ics_prefix);
3456                     SendToICS("refresh\n");
3457                 }
3458                 continue;
3459             }
3460
3461             /* Error messages */
3462 //          if (ics_user_moved) {
3463             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3464                 if (looking_at(buf, &i, "Illegal move") ||
3465                     looking_at(buf, &i, "Not a legal move") ||
3466                     looking_at(buf, &i, "Your king is in check") ||
3467                     looking_at(buf, &i, "It isn't your turn") ||
3468                     looking_at(buf, &i, "It is not your move")) {
3469                     /* Illegal move */
3470                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3471                         currentMove = forwardMostMove-1;
3472                         DisplayMove(currentMove - 1); /* before DMError */
3473                         DrawPosition(FALSE, boards[currentMove]);
3474                         SwitchClocks(forwardMostMove-1); // [HGM] race
3475                         DisplayBothClocks();
3476                     }
3477                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3478                     ics_user_moved = 0;
3479                     continue;
3480                 }
3481             }
3482
3483             if (looking_at(buf, &i, "still have time") ||
3484                 looking_at(buf, &i, "not out of time") ||
3485                 looking_at(buf, &i, "either player is out of time") ||
3486                 looking_at(buf, &i, "has timeseal; checking")) {
3487                 /* We must have called his flag a little too soon */
3488                 whiteFlag = blackFlag = FALSE;
3489                 continue;
3490             }
3491
3492             if (looking_at(buf, &i, "added * seconds to") ||
3493                 looking_at(buf, &i, "seconds were added to")) {
3494                 /* Update the clocks */
3495                 SendToICS(ics_prefix);
3496                 SendToICS("refresh\n");
3497                 continue;
3498             }
3499
3500             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3501                 ics_clock_paused = TRUE;
3502                 StopClocks();
3503                 continue;
3504             }
3505
3506             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3507                 ics_clock_paused = FALSE;
3508                 StartClocks();
3509                 continue;
3510             }
3511
3512             /* Grab player ratings from the Creating: message.
3513                Note we have to check for the special case when
3514                the ICS inserts things like [white] or [black]. */
3515             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3516                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3517                 /* star_matches:
3518                    0    player 1 name (not necessarily white)
3519                    1    player 1 rating
3520                    2    empty, white, or black (IGNORED)
3521                    3    player 2 name (not necessarily black)
3522                    4    player 2 rating
3523
3524                    The names/ratings are sorted out when the game
3525                    actually starts (below).
3526                 */
3527                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3528                 player1Rating = string_to_rating(star_match[1]);
3529                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3530                 player2Rating = string_to_rating(star_match[4]);
3531
3532                 if (appData.debugMode)
3533                   fprintf(debugFP,
3534                           "Ratings from 'Creating:' %s %d, %s %d\n",
3535                           player1Name, player1Rating,
3536                           player2Name, player2Rating);
3537
3538                 continue;
3539             }
3540
3541             /* Improved generic start/end-of-game messages */
3542             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3543                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3544                 /* If tkind == 0: */
3545                 /* star_match[0] is the game number */
3546                 /*           [1] is the white player's name */
3547                 /*           [2] is the black player's name */
3548                 /* For end-of-game: */
3549                 /*           [3] is the reason for the game end */
3550                 /*           [4] is a PGN end game-token, preceded by " " */
3551                 /* For start-of-game: */
3552                 /*           [3] begins with "Creating" or "Continuing" */
3553                 /*           [4] is " *" or empty (don't care). */
3554                 int gamenum = atoi(star_match[0]);
3555                 char *whitename, *blackname, *why, *endtoken;
3556                 ChessMove endtype = EndOfFile;
3557
3558                 if (tkind == 0) {
3559                   whitename = star_match[1];
3560                   blackname = star_match[2];
3561                   why = star_match[3];
3562                   endtoken = star_match[4];
3563                 } else {
3564                   whitename = star_match[1];
3565                   blackname = star_match[3];
3566                   why = star_match[5];
3567                   endtoken = star_match[6];
3568                 }
3569
3570                 /* Game start messages */
3571                 if (strncmp(why, "Creating ", 9) == 0 ||
3572                     strncmp(why, "Continuing ", 11) == 0) {
3573                     gs_gamenum = gamenum;
3574                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3575                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3576 #if ZIPPY
3577                     if (appData.zippyPlay) {
3578                         ZippyGameStart(whitename, blackname);
3579                     }
3580 #endif /*ZIPPY*/
3581                     partnerBoardValid = FALSE; // [HGM] bughouse
3582                     continue;
3583                 }
3584
3585                 /* Game end messages */
3586                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3587                     ics_gamenum != gamenum) {
3588                     continue;
3589                 }
3590                 while (endtoken[0] == ' ') endtoken++;
3591                 switch (endtoken[0]) {
3592                   case '*':
3593                   default:
3594                     endtype = GameUnfinished;
3595                     break;
3596                   case '0':
3597                     endtype = BlackWins;
3598                     break;
3599                   case '1':
3600                     if (endtoken[1] == '/')
3601                       endtype = GameIsDrawn;
3602                     else
3603                       endtype = WhiteWins;
3604                     break;
3605                 }
3606                 GameEnds(endtype, why, GE_ICS);
3607 #if ZIPPY
3608                 if (appData.zippyPlay && first.initDone) {
3609                     ZippyGameEnd(endtype, why);
3610                     if (first.pr == NULL) {
3611                       /* Start the next process early so that we'll
3612                          be ready for the next challenge */
3613                       StartChessProgram(&first);
3614                     }
3615                     /* Send "new" early, in case this command takes
3616                        a long time to finish, so that we'll be ready
3617                        for the next challenge. */
3618                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3619                     Reset(TRUE, TRUE);
3620                 }
3621 #endif /*ZIPPY*/
3622                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "Removing game * from observation") ||
3627                 looking_at(buf, &i, "no longer observing game *") ||
3628                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3629                 if (gameMode == IcsObserving &&
3630                     atoi(star_match[0]) == ics_gamenum)
3631                   {
3632                       /* icsEngineAnalyze */
3633                       if (appData.icsEngineAnalyze) {
3634                             ExitAnalyzeMode();
3635                             ModeHighlight();
3636                       }
3637                       StopClocks();
3638                       gameMode = IcsIdle;
3639                       ics_gamenum = -1;
3640                       ics_user_moved = FALSE;
3641                   }
3642                 continue;
3643             }
3644
3645             if (looking_at(buf, &i, "no longer examining game *")) {
3646                 if (gameMode == IcsExamining &&
3647                     atoi(star_match[0]) == ics_gamenum)
3648                   {
3649                       gameMode = IcsIdle;
3650                       ics_gamenum = -1;
3651                       ics_user_moved = FALSE;
3652                   }
3653                 continue;
3654             }
3655
3656             /* Advance leftover_start past any newlines we find,
3657                so only partial lines can get reparsed */
3658             if (looking_at(buf, &i, "\n")) {
3659                 prevColor = curColor;
3660                 if (curColor != ColorNormal) {
3661                     if (oldi > next_out) {
3662                         SendToPlayer(&buf[next_out], oldi - next_out);
3663                         next_out = oldi;
3664                     }
3665                     Colorize(ColorNormal, FALSE);
3666                     curColor = ColorNormal;
3667                 }
3668                 if (started == STARTED_BOARD) {
3669                     started = STARTED_NONE;
3670                     parse[parse_pos] = NULLCHAR;
3671                     ParseBoard12(parse);
3672                     ics_user_moved = 0;
3673
3674                     /* Send premove here */
3675                     if (appData.premove) {
3676                       char str[MSG_SIZ];
3677                       if (currentMove == 0 &&
3678                           gameMode == IcsPlayingWhite &&
3679                           appData.premoveWhite) {
3680                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3681                         if (appData.debugMode)
3682                           fprintf(debugFP, "Sending premove:\n");
3683                         SendToICS(str);
3684                       } else if (currentMove == 1 &&
3685                                  gameMode == IcsPlayingBlack &&
3686                                  appData.premoveBlack) {
3687                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3688                         if (appData.debugMode)
3689                           fprintf(debugFP, "Sending premove:\n");
3690                         SendToICS(str);
3691                       } else if (gotPremove) {
3692                         gotPremove = 0;
3693                         ClearPremoveHighlights();
3694                         if (appData.debugMode)
3695                           fprintf(debugFP, "Sending premove:\n");
3696                           UserMoveEvent(premoveFromX, premoveFromY,
3697                                         premoveToX, premoveToY,
3698                                         premovePromoChar);
3699                       }
3700                     }
3701
3702                     /* Usually suppress following prompt */
3703                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3704                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3705                         if (looking_at(buf, &i, "*% ")) {
3706                             savingComment = FALSE;
3707                             suppressKibitz = 0;
3708                         }
3709                     }
3710                     next_out = i;
3711                 } else if (started == STARTED_HOLDINGS) {
3712                     int gamenum;
3713                     char new_piece[MSG_SIZ];
3714                     started = STARTED_NONE;
3715                     parse[parse_pos] = NULLCHAR;
3716                     if (appData.debugMode)
3717                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3718                                                         parse, currentMove);
3719                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3720                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3721                         if (gameInfo.variant == VariantNormal) {
3722                           /* [HGM] We seem to switch variant during a game!
3723                            * Presumably no holdings were displayed, so we have
3724                            * to move the position two files to the right to
3725                            * create room for them!
3726                            */
3727                           VariantClass newVariant;
3728                           switch(gameInfo.boardWidth) { // base guess on board width
3729                                 case 9:  newVariant = VariantShogi; break;
3730                                 case 10: newVariant = VariantGreat; break;
3731                                 default: newVariant = VariantCrazyhouse; break;
3732                           }
3733                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3734                           /* Get a move list just to see the header, which
3735                              will tell us whether this is really bug or zh */
3736                           if (ics_getting_history == H_FALSE) {
3737                             ics_getting_history = H_REQUESTED;
3738                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3739                             SendToICS(str);
3740                           }
3741                         }
3742                         new_piece[0] = NULLCHAR;
3743                         sscanf(parse, "game %d white [%s black [%s <- %s",
3744                                &gamenum, white_holding, black_holding,
3745                                new_piece);
3746                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3747                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3748                         /* [HGM] copy holdings to board holdings area */
3749                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3750                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3751                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3752 #if ZIPPY
3753                         if (appData.zippyPlay && first.initDone) {
3754                             ZippyHoldings(white_holding, black_holding,
3755                                           new_piece);
3756                         }
3757 #endif /*ZIPPY*/
3758                         if (tinyLayout || smallLayout) {
3759                             char wh[16], bh[16];
3760                             PackHolding(wh, white_holding);
3761                             PackHolding(bh, black_holding);
3762                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3763                                     gameInfo.white, gameInfo.black);
3764                         } else {
3765                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3766                                     gameInfo.white, white_holding,
3767                                     gameInfo.black, black_holding);
3768                         }
3769                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3770                         DrawPosition(FALSE, boards[currentMove]);
3771                         DisplayTitle(str);
3772                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3773                         sscanf(parse, "game %d white [%s black [%s <- %s",
3774                                &gamenum, white_holding, black_holding,
3775                                new_piece);
3776                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3777                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3778                         /* [HGM] copy holdings to partner-board holdings area */
3779                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3780                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3781                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3782                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3783                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3784                       }
3785                     }
3786                     /* Suppress following prompt */
3787                     if (looking_at(buf, &i, "*% ")) {
3788                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3789                         savingComment = FALSE;
3790                         suppressKibitz = 0;
3791                     }
3792                     next_out = i;
3793                 }
3794                 continue;
3795             }
3796
3797             i++;                /* skip unparsed character and loop back */
3798         }
3799
3800         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3801 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3802 //          SendToPlayer(&buf[next_out], i - next_out);
3803             started != STARTED_HOLDINGS && leftover_start > next_out) {
3804             SendToPlayer(&buf[next_out], leftover_start - next_out);
3805             next_out = i;
3806         }
3807
3808         leftover_len = buf_len - leftover_start;
3809         /* if buffer ends with something we couldn't parse,
3810            reparse it after appending the next read */
3811
3812     } else if (count == 0) {
3813         RemoveInputSource(isr);
3814         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3815     } else {
3816         DisplayFatalError(_("Error reading from ICS"), error, 1);
3817     }
3818 }
3819
3820
3821 /* Board style 12 looks like this:
3822
3823    <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
3824
3825  * The "<12> " is stripped before it gets to this routine.  The two
3826  * trailing 0's (flip state and clock ticking) are later addition, and
3827  * some chess servers may not have them, or may have only the first.
3828  * Additional trailing fields may be added in the future.
3829  */
3830
3831 #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"
3832
3833 #define RELATION_OBSERVING_PLAYED    0
3834 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3835 #define RELATION_PLAYING_MYMOVE      1
3836 #define RELATION_PLAYING_NOTMYMOVE  -1
3837 #define RELATION_EXAMINING           2
3838 #define RELATION_ISOLATED_BOARD     -3
3839 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3840
3841 void
3842 ParseBoard12(string)
3843      char *string;
3844 {
3845     GameMode newGameMode;
3846     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3847     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3848     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3849     char to_play, board_chars[200];
3850     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3851     char black[32], white[32];
3852     Board board;
3853     int prevMove = currentMove;
3854     int ticking = 2;
3855     ChessMove moveType;
3856     int fromX, fromY, toX, toY;
3857     char promoChar;
3858     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3859     char *bookHit = NULL; // [HGM] book
3860     Boolean weird = FALSE, reqFlag = FALSE;
3861
3862     fromX = fromY = toX = toY = -1;
3863
3864     newGame = FALSE;
3865
3866     if (appData.debugMode)
3867       fprintf(debugFP, _("Parsing board: %s\n"), string);
3868
3869     move_str[0] = NULLCHAR;
3870     elapsed_time[0] = NULLCHAR;
3871     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3872         int  i = 0, j;
3873         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3874             if(string[i] == ' ') { ranks++; files = 0; }
3875             else files++;
3876             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3877             i++;
3878         }
3879         for(j = 0; j <i; j++) board_chars[j] = string[j];
3880         board_chars[i] = '\0';
3881         string += i + 1;
3882     }
3883     n = sscanf(string, PATTERN, &to_play, &double_push,
3884                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3885                &gamenum, white, black, &relation, &basetime, &increment,
3886                &white_stren, &black_stren, &white_time, &black_time,
3887                &moveNum, str, elapsed_time, move_str, &ics_flip,
3888                &ticking);
3889
3890     if (n < 21) {
3891         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3892         DisplayError(str, 0);
3893         return;
3894     }
3895
3896     /* Convert the move number to internal form */
3897     moveNum = (moveNum - 1) * 2;
3898     if (to_play == 'B') moveNum++;
3899     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3900       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3901                         0, 1);
3902       return;
3903     }
3904
3905     switch (relation) {
3906       case RELATION_OBSERVING_PLAYED:
3907       case RELATION_OBSERVING_STATIC:
3908         if (gamenum == -1) {
3909             /* Old ICC buglet */
3910             relation = RELATION_OBSERVING_STATIC;
3911         }
3912         newGameMode = IcsObserving;
3913         break;
3914       case RELATION_PLAYING_MYMOVE:
3915       case RELATION_PLAYING_NOTMYMOVE:
3916         newGameMode =
3917           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3918             IcsPlayingWhite : IcsPlayingBlack;
3919         break;
3920       case RELATION_EXAMINING:
3921         newGameMode = IcsExamining;
3922         break;
3923       case RELATION_ISOLATED_BOARD:
3924       default:
3925         /* Just display this board.  If user was doing something else,
3926            we will forget about it until the next board comes. */
3927         newGameMode = IcsIdle;
3928         break;
3929       case RELATION_STARTING_POSITION:
3930         newGameMode = gameMode;
3931         break;
3932     }
3933
3934     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3935          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3936       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3937       char *toSqr;
3938       for (k = 0; k < ranks; k++) {
3939         for (j = 0; j < files; j++)
3940           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3941         if(gameInfo.holdingsWidth > 1) {
3942              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3943              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3944         }
3945       }
3946       CopyBoard(partnerBoard, board);
3947       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3948         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3949         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3950       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3951       if(toSqr = strchr(str, '-')) {
3952         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3953         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3954       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3955       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3956       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3957       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3958       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3959       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3960                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3961       DisplayMessage(partnerStatus, "");
3962         partnerBoardValid = TRUE;
3963       return;
3964     }
3965
3966     /* Modify behavior for initial board display on move listing
3967        of wild games.
3968        */
3969     switch (ics_getting_history) {
3970       case H_FALSE:
3971       case H_REQUESTED:
3972         break;
3973       case H_GOT_REQ_HEADER:
3974       case H_GOT_UNREQ_HEADER:
3975         /* This is the initial position of the current game */
3976         gamenum = ics_gamenum;
3977         moveNum = 0;            /* old ICS bug workaround */
3978         if (to_play == 'B') {
3979           startedFromSetupPosition = TRUE;
3980           blackPlaysFirst = TRUE;
3981           moveNum = 1;
3982           if (forwardMostMove == 0) forwardMostMove = 1;
3983           if (backwardMostMove == 0) backwardMostMove = 1;
3984           if (currentMove == 0) currentMove = 1;
3985         }
3986         newGameMode = gameMode;
3987         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3988         break;
3989       case H_GOT_UNWANTED_HEADER:
3990         /* This is an initial board that we don't want */
3991         return;
3992       case H_GETTING_MOVES:
3993         /* Should not happen */
3994         DisplayError(_("Error gathering move list: extra board"), 0);
3995         ics_getting_history = H_FALSE;
3996         return;
3997     }
3998
3999    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4000                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4001      /* [HGM] We seem to have switched variant unexpectedly
4002       * Try to guess new variant from board size
4003       */
4004           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4005           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4006           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4007           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4008           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4009           if(!weird) newVariant = VariantNormal;
4010           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4011           /* Get a move list just to see the header, which
4012              will tell us whether this is really bug or zh */
4013           if (ics_getting_history == H_FALSE) {
4014             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4015             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4016             SendToICS(str);
4017           }
4018     }
4019
4020     /* Take action if this is the first board of a new game, or of a
4021        different game than is currently being displayed.  */
4022     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4023         relation == RELATION_ISOLATED_BOARD) {
4024
4025         /* Forget the old game and get the history (if any) of the new one */
4026         if (gameMode != BeginningOfGame) {
4027           Reset(TRUE, TRUE);
4028         }
4029         newGame = TRUE;
4030         if (appData.autoRaiseBoard) BoardToTop();
4031         prevMove = -3;
4032         if (gamenum == -1) {
4033             newGameMode = IcsIdle;
4034         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4035                    appData.getMoveList && !reqFlag) {
4036             /* Need to get game history */
4037             ics_getting_history = H_REQUESTED;
4038             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4039             SendToICS(str);
4040         }
4041
4042         /* Initially flip the board to have black on the bottom if playing
4043            black or if the ICS flip flag is set, but let the user change
4044            it with the Flip View button. */
4045         flipView = appData.autoFlipView ?
4046           (newGameMode == IcsPlayingBlack) || ics_flip :
4047           appData.flipView;
4048
4049         /* Done with values from previous mode; copy in new ones */
4050         gameMode = newGameMode;
4051         ModeHighlight();
4052         ics_gamenum = gamenum;
4053         if (gamenum == gs_gamenum) {
4054             int klen = strlen(gs_kind);
4055             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4056             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4057             gameInfo.event = StrSave(str);
4058         } else {
4059             gameInfo.event = StrSave("ICS game");
4060         }
4061         gameInfo.site = StrSave(appData.icsHost);
4062         gameInfo.date = PGNDate();
4063         gameInfo.round = StrSave("-");
4064         gameInfo.white = StrSave(white);
4065         gameInfo.black = StrSave(black);
4066         timeControl = basetime * 60 * 1000;
4067         timeControl_2 = 0;
4068         timeIncrement = increment * 1000;
4069         movesPerSession = 0;
4070         gameInfo.timeControl = TimeControlTagValue();
4071         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4072   if (appData.debugMode) {
4073     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4074     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4075     setbuf(debugFP, NULL);
4076   }
4077
4078         gameInfo.outOfBook = NULL;
4079
4080         /* Do we have the ratings? */
4081         if (strcmp(player1Name, white) == 0 &&
4082             strcmp(player2Name, black) == 0) {
4083             if (appData.debugMode)
4084               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4085                       player1Rating, player2Rating);
4086             gameInfo.whiteRating = player1Rating;
4087             gameInfo.blackRating = player2Rating;
4088         } else if (strcmp(player2Name, white) == 0 &&
4089                    strcmp(player1Name, black) == 0) {
4090             if (appData.debugMode)
4091               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4092                       player2Rating, player1Rating);
4093             gameInfo.whiteRating = player2Rating;
4094             gameInfo.blackRating = player1Rating;
4095         }
4096         player1Name[0] = player2Name[0] = NULLCHAR;
4097
4098         /* Silence shouts if requested */
4099         if (appData.quietPlay &&
4100             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4101             SendToICS(ics_prefix);
4102             SendToICS("set shout 0\n");
4103         }
4104     }
4105
4106     /* Deal with midgame name changes */
4107     if (!newGame) {
4108         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4109             if (gameInfo.white) free(gameInfo.white);
4110             gameInfo.white = StrSave(white);
4111         }
4112         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4113             if (gameInfo.black) free(gameInfo.black);
4114             gameInfo.black = StrSave(black);
4115         }
4116     }
4117
4118     /* Throw away game result if anything actually changes in examine mode */
4119     if (gameMode == IcsExamining && !newGame) {
4120         gameInfo.result = GameUnfinished;
4121         if (gameInfo.resultDetails != NULL) {
4122             free(gameInfo.resultDetails);
4123             gameInfo.resultDetails = NULL;
4124         }
4125     }
4126
4127     /* In pausing && IcsExamining mode, we ignore boards coming
4128        in if they are in a different variation than we are. */
4129     if (pauseExamInvalid) return;
4130     if (pausing && gameMode == IcsExamining) {
4131         if (moveNum <= pauseExamForwardMostMove) {
4132             pauseExamInvalid = TRUE;
4133             forwardMostMove = pauseExamForwardMostMove;
4134             return;
4135         }
4136     }
4137
4138   if (appData.debugMode) {
4139     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4140   }
4141     /* Parse the board */
4142     for (k = 0; k < ranks; k++) {
4143       for (j = 0; j < files; j++)
4144         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4145       if(gameInfo.holdingsWidth > 1) {
4146            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4147            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4148       }
4149     }
4150     CopyBoard(boards[moveNum], board);
4151     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4152     if (moveNum == 0) {
4153         startedFromSetupPosition =
4154           !CompareBoards(board, initialPosition);
4155         if(startedFromSetupPosition)
4156             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4157     }
4158
4159     /* [HGM] Set castling rights. Take the outermost Rooks,
4160        to make it also work for FRC opening positions. Note that board12
4161        is really defective for later FRC positions, as it has no way to
4162        indicate which Rook can castle if they are on the same side of King.
4163        For the initial position we grant rights to the outermost Rooks,
4164        and remember thos rights, and we then copy them on positions
4165        later in an FRC game. This means WB might not recognize castlings with
4166        Rooks that have moved back to their original position as illegal,
4167        but in ICS mode that is not its job anyway.
4168     */
4169     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4170     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4171
4172         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4173             if(board[0][i] == WhiteRook) j = i;
4174         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4175         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4176             if(board[0][i] == WhiteRook) j = i;
4177         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4178         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4179             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4180         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4181         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4182             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4183         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4184
4185         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4188         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4189             if(board[BOARD_HEIGHT-1][k] == bKing)
4190                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4191         if(gameInfo.variant == VariantTwoKings) {
4192             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4193             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4194             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4195         }
4196     } else { int r;
4197         r = boards[moveNum][CASTLING][0] = initialRights[0];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4199         r = boards[moveNum][CASTLING][1] = initialRights[1];
4200         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4201         r = boards[moveNum][CASTLING][3] = initialRights[3];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4203         r = boards[moveNum][CASTLING][4] = initialRights[4];
4204         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4205         /* wildcastle kludge: always assume King has rights */
4206         r = boards[moveNum][CASTLING][2] = initialRights[2];
4207         r = boards[moveNum][CASTLING][5] = initialRights[5];
4208     }
4209     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4210     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4211
4212
4213     if (ics_getting_history == H_GOT_REQ_HEADER ||
4214         ics_getting_history == H_GOT_UNREQ_HEADER) {
4215         /* This was an initial position from a move list, not
4216            the current position */
4217         return;
4218     }
4219
4220     /* Update currentMove and known move number limits */
4221     newMove = newGame || moveNum > forwardMostMove;
4222
4223     if (newGame) {
4224         forwardMostMove = backwardMostMove = currentMove = moveNum;
4225         if (gameMode == IcsExamining && moveNum == 0) {
4226           /* Workaround for ICS limitation: we are not told the wild
4227              type when starting to examine a game.  But if we ask for
4228              the move list, the move list header will tell us */
4229             ics_getting_history = H_REQUESTED;
4230             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4231             SendToICS(str);
4232         }
4233     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4234                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4235 #if ZIPPY
4236         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4237         /* [HGM] applied this also to an engine that is silently watching        */
4238         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4239             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4240             gameInfo.variant == currentlyInitializedVariant) {
4241           takeback = forwardMostMove - moveNum;
4242           for (i = 0; i < takeback; i++) {
4243             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4244             SendToProgram("undo\n", &first);
4245           }
4246         }
4247 #endif
4248
4249         forwardMostMove = moveNum;
4250         if (!pausing || currentMove > forwardMostMove)
4251           currentMove = forwardMostMove;
4252     } else {
4253         /* New part of history that is not contiguous with old part */
4254         if (pausing && gameMode == IcsExamining) {
4255             pauseExamInvalid = TRUE;
4256             forwardMostMove = pauseExamForwardMostMove;
4257             return;
4258         }
4259         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4260 #if ZIPPY
4261             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4262                 // [HGM] when we will receive the move list we now request, it will be
4263                 // fed to the engine from the first move on. So if the engine is not
4264                 // in the initial position now, bring it there.
4265                 InitChessProgram(&first, 0);
4266             }
4267 #endif
4268             ics_getting_history = H_REQUESTED;
4269             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4270             SendToICS(str);
4271         }
4272         forwardMostMove = backwardMostMove = currentMove = moveNum;
4273     }
4274
4275     /* Update the clocks */
4276     if (strchr(elapsed_time, '.')) {
4277       /* Time is in ms */
4278       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4279       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4280     } else {
4281       /* Time is in seconds */
4282       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4283       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4284     }
4285
4286
4287 #if ZIPPY
4288     if (appData.zippyPlay && newGame &&
4289         gameMode != IcsObserving && gameMode != IcsIdle &&
4290         gameMode != IcsExamining)
4291       ZippyFirstBoard(moveNum, basetime, increment);
4292 #endif
4293
4294     /* Put the move on the move list, first converting
4295        to canonical algebraic form. */
4296     if (moveNum > 0) {
4297   if (appData.debugMode) {
4298     if (appData.debugMode) { int f = forwardMostMove;
4299         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4300                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4301                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4302     }
4303     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4304     fprintf(debugFP, "moveNum = %d\n", moveNum);
4305     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4306     setbuf(debugFP, NULL);
4307   }
4308         if (moveNum <= backwardMostMove) {
4309             /* We don't know what the board looked like before
4310                this move.  Punt. */
4311           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4312             strcat(parseList[moveNum - 1], " ");
4313             strcat(parseList[moveNum - 1], elapsed_time);
4314             moveList[moveNum - 1][0] = NULLCHAR;
4315         } else if (strcmp(move_str, "none") == 0) {
4316             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4317             /* Again, we don't know what the board looked like;
4318                this is really the start of the game. */
4319             parseList[moveNum - 1][0] = NULLCHAR;
4320             moveList[moveNum - 1][0] = NULLCHAR;
4321             backwardMostMove = moveNum;
4322             startedFromSetupPosition = TRUE;
4323             fromX = fromY = toX = toY = -1;
4324         } else {
4325           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4326           //                 So we parse the long-algebraic move string in stead of the SAN move
4327           int valid; char buf[MSG_SIZ], *prom;
4328
4329           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4330                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4331           // str looks something like "Q/a1-a2"; kill the slash
4332           if(str[1] == '/')
4333             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4334           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4335           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4336                 strcat(buf, prom); // long move lacks promo specification!
4337           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4338                 if(appData.debugMode)
4339                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4340                 safeStrCpy(move_str, buf, MSG_SIZ);
4341           }
4342           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar)
4344                || ParseOneMove(buf, moveNum - 1, &moveType,
4345                                 &fromX, &fromY, &toX, &toY, &promoChar);
4346           // end of long SAN patch
4347           if (valid) {
4348             (void) CoordsToAlgebraic(boards[moveNum - 1],
4349                                      PosFlags(moveNum - 1),
4350                                      fromY, fromX, toY, toX, promoChar,
4351                                      parseList[moveNum-1]);
4352             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4353               case MT_NONE:
4354               case MT_STALEMATE:
4355               default:
4356                 break;
4357               case MT_CHECK:
4358                 if(gameInfo.variant != VariantShogi)
4359                     strcat(parseList[moveNum - 1], "+");
4360                 break;
4361               case MT_CHECKMATE:
4362               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4363                 strcat(parseList[moveNum - 1], "#");
4364                 break;
4365             }
4366             strcat(parseList[moveNum - 1], " ");
4367             strcat(parseList[moveNum - 1], elapsed_time);
4368             /* currentMoveString is set as a side-effect of ParseOneMove */
4369             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4370             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4371             strcat(moveList[moveNum - 1], "\n");
4372
4373             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4374                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4375               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4376                 ChessSquare old, new = boards[moveNum][k][j];
4377                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4378                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4379                   if(old == new) continue;
4380                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4381                   else if(new == WhiteWazir || new == BlackWazir) {
4382                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4383                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4384                       else boards[moveNum][k][j] = old; // preserve type of Gold
4385                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4386                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4387               }
4388           } else {
4389             /* Move from ICS was illegal!?  Punt. */
4390             if (appData.debugMode) {
4391               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4392               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4393             }
4394             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4395             strcat(parseList[moveNum - 1], " ");
4396             strcat(parseList[moveNum - 1], elapsed_time);
4397             moveList[moveNum - 1][0] = NULLCHAR;
4398             fromX = fromY = toX = toY = -1;
4399           }
4400         }
4401   if (appData.debugMode) {
4402     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4403     setbuf(debugFP, NULL);
4404   }
4405
4406 #if ZIPPY
4407         /* Send move to chess program (BEFORE animating it). */
4408         if (appData.zippyPlay && !newGame && newMove &&
4409            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4410
4411             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4412                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4413                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4414                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4415                             move_str);
4416                     DisplayError(str, 0);
4417                 } else {
4418                     if (first.sendTime) {
4419                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4420                     }
4421                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4422                     if (firstMove && !bookHit) {
4423                         firstMove = FALSE;
4424                         if (first.useColors) {
4425                           SendToProgram(gameMode == IcsPlayingWhite ?
4426                                         "white\ngo\n" :
4427                                         "black\ngo\n", &first);
4428                         } else {
4429                           SendToProgram("go\n", &first);
4430                         }
4431                         first.maybeThinking = TRUE;
4432                     }
4433                 }
4434             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4435               if (moveList[moveNum - 1][0] == NULLCHAR) {
4436                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4437                 DisplayError(str, 0);
4438               } else {
4439                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4440                 SendMoveToProgram(moveNum - 1, &first);
4441               }
4442             }
4443         }
4444 #endif
4445     }
4446
4447     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4448         /* If move comes from a remote source, animate it.  If it
4449            isn't remote, it will have already been animated. */
4450         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4451             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4452         }
4453         if (!pausing && appData.highlightLastMove) {
4454             SetHighlights(fromX, fromY, toX, toY);
4455         }
4456     }
4457
4458     /* Start the clocks */
4459     whiteFlag = blackFlag = FALSE;
4460     appData.clockMode = !(basetime == 0 && increment == 0);
4461     if (ticking == 0) {
4462       ics_clock_paused = TRUE;
4463       StopClocks();
4464     } else if (ticking == 1) {
4465       ics_clock_paused = FALSE;
4466     }
4467     if (gameMode == IcsIdle ||
4468         relation == RELATION_OBSERVING_STATIC ||
4469         relation == RELATION_EXAMINING ||
4470         ics_clock_paused)
4471       DisplayBothClocks();
4472     else
4473       StartClocks();
4474
4475     /* Display opponents and material strengths */
4476     if (gameInfo.variant != VariantBughouse &&
4477         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4478         if (tinyLayout || smallLayout) {
4479             if(gameInfo.variant == VariantNormal)
4480               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4481                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4482                     basetime, increment);
4483             else
4484               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4485                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4486                     basetime, increment, (int) gameInfo.variant);
4487         } else {
4488             if(gameInfo.variant == VariantNormal)
4489               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4490                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4491                     basetime, increment);
4492             else
4493               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4494                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4495                     basetime, increment, VariantName(gameInfo.variant));
4496         }
4497         DisplayTitle(str);
4498   if (appData.debugMode) {
4499     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4500   }
4501     }
4502
4503
4504     /* Display the board */
4505     if (!pausing && !appData.noGUI) {
4506
4507       if (appData.premove)
4508           if (!gotPremove ||
4509              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4510              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4511               ClearPremoveHighlights();
4512
4513       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4514         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4515       DrawPosition(j, boards[currentMove]);
4516
4517       DisplayMove(moveNum - 1);
4518       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4519             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4520               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4521         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4522       }
4523     }
4524
4525     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4526 #if ZIPPY
4527     if(bookHit) { // [HGM] book: simulate book reply
4528         static char bookMove[MSG_SIZ]; // a bit generous?
4529
4530         programStats.nodes = programStats.depth = programStats.time =
4531         programStats.score = programStats.got_only_move = 0;
4532         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4533
4534         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4535         strcat(bookMove, bookHit);
4536         HandleMachineMove(bookMove, &first);
4537     }
4538 #endif
4539 }
4540
4541 void
4542 GetMoveListEvent()
4543 {
4544     char buf[MSG_SIZ];
4545     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4546         ics_getting_history = H_REQUESTED;
4547         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4548         SendToICS(buf);
4549     }
4550 }
4551
4552 void
4553 AnalysisPeriodicEvent(force)
4554      int force;
4555 {
4556     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4557          && !force) || !appData.periodicUpdates)
4558       return;
4559
4560     /* Send . command to Crafty to collect stats */
4561     SendToProgram(".\n", &first);
4562
4563     /* Don't send another until we get a response (this makes
4564        us stop sending to old Crafty's which don't understand
4565        the "." command (sending illegal cmds resets node count & time,
4566        which looks bad)) */
4567     programStats.ok_to_send = 0;
4568 }
4569
4570 void ics_update_width(new_width)
4571         int new_width;
4572 {
4573         ics_printf("set width %d\n", new_width);
4574 }
4575
4576 void
4577 SendMoveToProgram(moveNum, cps)
4578      int moveNum;
4579      ChessProgramState *cps;
4580 {
4581     char buf[MSG_SIZ];
4582
4583     if (cps->useUsermove) {
4584       SendToProgram("usermove ", cps);
4585     }
4586     if (cps->useSAN) {
4587       char *space;
4588       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4589         int len = space - parseList[moveNum];
4590         memcpy(buf, parseList[moveNum], len);
4591         buf[len++] = '\n';
4592         buf[len] = NULLCHAR;
4593       } else {
4594         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4595       }
4596       SendToProgram(buf, cps);
4597     } else {
4598       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4599         AlphaRank(moveList[moveNum], 4);
4600         SendToProgram(moveList[moveNum], cps);
4601         AlphaRank(moveList[moveNum], 4); // and back
4602       } else
4603       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4604        * the engine. It would be nice to have a better way to identify castle
4605        * moves here. */
4606       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4607                                                                          && cps->useOOCastle) {
4608         int fromX = moveList[moveNum][0] - AAA;
4609         int fromY = moveList[moveNum][1] - ONE;
4610         int toX = moveList[moveNum][2] - AAA;
4611         int toY = moveList[moveNum][3] - ONE;
4612         if((boards[moveNum][fromY][fromX] == WhiteKing
4613             && boards[moveNum][toY][toX] == WhiteRook)
4614            || (boards[moveNum][fromY][fromX] == BlackKing
4615                && boards[moveNum][toY][toX] == BlackRook)) {
4616           if(toX > fromX) SendToProgram("O-O\n", cps);
4617           else SendToProgram("O-O-O\n", cps);
4618         }
4619         else SendToProgram(moveList[moveNum], cps);
4620       }
4621       else SendToProgram(moveList[moveNum], cps);
4622       /* End of additions by Tord */
4623     }
4624
4625     /* [HGM] setting up the opening has brought engine in force mode! */
4626     /*       Send 'go' if we are in a mode where machine should play. */
4627     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4628         (gameMode == TwoMachinesPlay   ||
4629 #if ZIPPY
4630          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4631 #endif
4632          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4633         SendToProgram("go\n", cps);
4634   if (appData.debugMode) {
4635     fprintf(debugFP, "(extra)\n");
4636   }
4637     }
4638     setboardSpoiledMachineBlack = 0;
4639 }
4640
4641 void
4642 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4643      ChessMove moveType;
4644      int fromX, fromY, toX, toY;
4645      char promoChar;
4646 {
4647     char user_move[MSG_SIZ];
4648
4649     switch (moveType) {
4650       default:
4651         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4652                 (int)moveType, fromX, fromY, toX, toY);
4653         DisplayError(user_move + strlen("say "), 0);
4654         break;
4655       case WhiteKingSideCastle:
4656       case BlackKingSideCastle:
4657       case WhiteQueenSideCastleWild:
4658       case BlackQueenSideCastleWild:
4659       /* PUSH Fabien */
4660       case WhiteHSideCastleFR:
4661       case BlackHSideCastleFR:
4662       /* POP Fabien */
4663         snprintf(user_move, MSG_SIZ, "o-o\n");
4664         break;
4665       case WhiteQueenSideCastle:
4666       case BlackQueenSideCastle:
4667       case WhiteKingSideCastleWild:
4668       case BlackKingSideCastleWild:
4669       /* PUSH Fabien */
4670       case WhiteASideCastleFR:
4671       case BlackASideCastleFR:
4672       /* POP Fabien */
4673         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4674         break;
4675       case WhiteNonPromotion:
4676       case BlackNonPromotion:
4677         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4678         break;
4679       case WhitePromotion:
4680       case BlackPromotion:
4681         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4682           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4683                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4684                 PieceToChar(WhiteFerz));
4685         else if(gameInfo.variant == VariantGreat)
4686           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4687                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4688                 PieceToChar(WhiteMan));
4689         else
4690           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4691                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4692                 promoChar);
4693         break;
4694       case WhiteDrop:
4695       case BlackDrop:
4696       drop:
4697         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4698                  ToUpper(PieceToChar((ChessSquare) fromX)),
4699                  AAA + toX, ONE + toY);
4700         break;
4701       case IllegalMove:  /* could be a variant we don't quite understand */
4702         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4703       case NormalMove:
4704       case WhiteCapturesEnPassant:
4705       case BlackCapturesEnPassant:
4706         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4707                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4708         break;
4709     }
4710     SendToICS(user_move);
4711     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4712         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4713 }
4714
4715 void
4716 UploadGameEvent()
4717 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4718     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4719     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4720     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4721         DisplayError("You cannot do this while you are playing or observing", 0);
4722         return;
4723     }
4724     if(gameMode != IcsExamining) { // is this ever not the case?
4725         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4726
4727         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4728           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4729         } else { // on FICS we must first go to general examine mode
4730           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4731         }
4732         if(gameInfo.variant != VariantNormal) {
4733             // try figure out wild number, as xboard names are not always valid on ICS
4734             for(i=1; i<=36; i++) {
4735               snprintf(buf, MSG_SIZ, "wild/%d", i);
4736                 if(StringToVariant(buf) == gameInfo.variant) break;
4737             }
4738             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4739             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4740             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4741         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4742         SendToICS(ics_prefix);
4743         SendToICS(buf);
4744         if(startedFromSetupPosition || backwardMostMove != 0) {
4745           fen = PositionToFEN(backwardMostMove, NULL);
4746           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4747             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4748             SendToICS(buf);
4749           } else { // FICS: everything has to set by separate bsetup commands
4750             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4751             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4752             SendToICS(buf);
4753             if(!WhiteOnMove(backwardMostMove)) {
4754                 SendToICS("bsetup tomove black\n");
4755             }
4756             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4757             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4760             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4761             SendToICS(buf);
4762             i = boards[backwardMostMove][EP_STATUS];
4763             if(i >= 0) { // set e.p.
4764               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4765                 SendToICS(buf);
4766             }
4767             bsetup++;
4768           }
4769         }
4770       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4771             SendToICS("bsetup done\n"); // switch to normal examining.
4772     }
4773     for(i = backwardMostMove; i<last; i++) {
4774         char buf[20];
4775         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4776         SendToICS(buf);
4777     }
4778     SendToICS(ics_prefix);
4779     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4780 }
4781
4782 void
4783 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4784      int rf, ff, rt, ft;
4785      char promoChar;
4786      char move[7];
4787 {
4788     if (rf == DROP_RANK) {
4789       sprintf(move, "%c@%c%c\n",
4790                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4791     } else {
4792         if (promoChar == 'x' || promoChar == NULLCHAR) {
4793           sprintf(move, "%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4795         } else {
4796             sprintf(move, "%c%c%c%c%c\n",
4797                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4798         }
4799     }
4800 }
4801
4802 void
4803 ProcessICSInitScript(f)
4804      FILE *f;
4805 {
4806     char buf[MSG_SIZ];
4807
4808     while (fgets(buf, MSG_SIZ, f)) {
4809         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4810     }
4811
4812     fclose(f);
4813 }
4814
4815
4816 void
4817 Sweep(int step)
4818 {
4819     ChessSquare piece = boards[currentMove][toY][toX];
4820     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4821     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4822     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4823     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4824     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4825     if(gameInfo.variant == VariantShogi) pawn = EmptySquare;
4826     do {
4827         promoSweep += step;
4828         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4829         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4830         else if(promoSweep == BlackPawn && step > 0) promoSweep = WhitePawn;
4831         else if(promoSweep == WhiteKing && step < 0) promoSweep = BlackKing;
4832         if(!step) step = 1;
4833     } while(promoSweep == king || promoSweep == pawn || PieceToChar(promoSweep) == '.'
4834                 || gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep);
4835     boards[currentMove][toY][toX] = promoSweep;
4836     DrawPosition(FALSE, boards[currentMove]);
4837     boards[currentMove][toY][toX] = piece;
4838 }
4839
4840 static int lastX, lastY;
4841
4842 void PromoScroll(int x, int y)
4843 {
4844   int step = 0;
4845   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
4846   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4847   if(!step) return;
4848   lastX = x; lastY = y;
4849
4850   if(promoSweep == EmptySquare) return;
4851   Sweep(step);
4852 }
4853
4854 void
4855 NextPiece(int step)
4856 {
4857     ChessSquare piece = boards[currentMove][toY][toX];
4858     do {
4859         pieceSweep += step;
4860         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4861         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4862         if(!step) step = 1;
4863     } while(PieceToChar(pieceSweep) == '.');
4864     boards[currentMove][toY][toX] = pieceSweep;
4865     DrawPosition(FALSE, boards[currentMove]);
4866     boards[currentMove][toY][toX] = piece;
4867 }
4868 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4869 void
4870 AlphaRank(char *move, int n)
4871 {
4872 //    char *p = move, c; int x, y;
4873
4874     if (appData.debugMode) {
4875         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4876     }
4877
4878     if(move[1]=='*' &&
4879        move[2]>='0' && move[2]<='9' &&
4880        move[3]>='a' && move[3]<='x'    ) {
4881         move[1] = '@';
4882         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4883         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4884     } else
4885     if(move[0]>='0' && move[0]<='9' &&
4886        move[1]>='a' && move[1]<='x' &&
4887        move[2]>='0' && move[2]<='9' &&
4888        move[3]>='a' && move[3]<='x'    ) {
4889         /* input move, Shogi -> normal */
4890         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4891         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4892         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4893         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4894     } else
4895     if(move[1]=='@' &&
4896        move[3]>='0' && move[3]<='9' &&
4897        move[2]>='a' && move[2]<='x'    ) {
4898         move[1] = '*';
4899         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4900         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4901     } else
4902     if(
4903        move[0]>='a' && move[0]<='x' &&
4904        move[3]>='0' && move[3]<='9' &&
4905        move[2]>='a' && move[2]<='x'    ) {
4906          /* output move, normal -> Shogi */
4907         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4908         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4909         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4910         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4911         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4912     }
4913     if (appData.debugMode) {
4914         fprintf(debugFP, "   out = '%s'\n", move);
4915     }
4916 }
4917
4918 char yy_textstr[8000];
4919
4920 /* Parser for moves from gnuchess, ICS, or user typein box */
4921 Boolean
4922 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4923      char *move;
4924      int moveNum;
4925      ChessMove *moveType;
4926      int *fromX, *fromY, *toX, *toY;
4927      char *promoChar;
4928 {
4929     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4930
4931     switch (*moveType) {
4932       case WhitePromotion:
4933       case BlackPromotion:
4934       case WhiteNonPromotion:
4935       case BlackNonPromotion:
4936       case NormalMove:
4937       case WhiteCapturesEnPassant:
4938       case BlackCapturesEnPassant:
4939       case WhiteKingSideCastle:
4940       case WhiteQueenSideCastle:
4941       case BlackKingSideCastle:
4942       case BlackQueenSideCastle:
4943       case WhiteKingSideCastleWild:
4944       case WhiteQueenSideCastleWild:
4945       case BlackKingSideCastleWild:
4946       case BlackQueenSideCastleWild:
4947       /* Code added by Tord: */
4948       case WhiteHSideCastleFR:
4949       case WhiteASideCastleFR:
4950       case BlackHSideCastleFR:
4951       case BlackASideCastleFR:
4952       /* End of code added by Tord */
4953       case IllegalMove:         /* bug or odd chess variant */
4954         *fromX = currentMoveString[0] - AAA;
4955         *fromY = currentMoveString[1] - ONE;
4956         *toX = currentMoveString[2] - AAA;
4957         *toY = currentMoveString[3] - ONE;
4958         *promoChar = currentMoveString[4];
4959         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4960             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4961     if (appData.debugMode) {
4962         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4963     }
4964             *fromX = *fromY = *toX = *toY = 0;
4965             return FALSE;
4966         }
4967         if (appData.testLegality) {
4968           return (*moveType != IllegalMove);
4969         } else {
4970           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4971                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4972         }
4973
4974       case WhiteDrop:
4975       case BlackDrop:
4976         *fromX = *moveType == WhiteDrop ?
4977           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4978           (int) CharToPiece(ToLower(currentMoveString[0]));
4979         *fromY = DROP_RANK;
4980         *toX = currentMoveString[2] - AAA;
4981         *toY = currentMoveString[3] - ONE;
4982         *promoChar = NULLCHAR;
4983         return TRUE;
4984
4985       case AmbiguousMove:
4986       case ImpossibleMove:
4987       case EndOfFile:
4988       case ElapsedTime:
4989       case Comment:
4990       case PGNTag:
4991       case NAG:
4992       case WhiteWins:
4993       case BlackWins:
4994       case GameIsDrawn:
4995       default:
4996     if (appData.debugMode) {
4997         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4998     }
4999         /* bug? */
5000         *fromX = *fromY = *toX = *toY = 0;
5001         *promoChar = NULLCHAR;
5002         return FALSE;
5003     }
5004 }
5005
5006
5007 void
5008 ParsePV(char *pv, Boolean storeComments)
5009 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5010   int fromX, fromY, toX, toY; char promoChar;
5011   ChessMove moveType;
5012   Boolean valid;
5013   int nr = 0;
5014
5015   endPV = forwardMostMove;
5016   do {
5017     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5018     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5019     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5020 if(appData.debugMode){
5021 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);
5022 }
5023     if(!valid && nr == 0 &&
5024        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5025         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5026         // Hande case where played move is different from leading PV move
5027         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5028         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5029         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5030         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5031           endPV += 2; // if position different, keep this
5032           moveList[endPV-1][0] = fromX + AAA;
5033           moveList[endPV-1][1] = fromY + ONE;
5034           moveList[endPV-1][2] = toX + AAA;
5035           moveList[endPV-1][3] = toY + ONE;
5036           parseList[endPV-1][0] = NULLCHAR;
5037           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5038         }
5039       }
5040     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5041     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5042     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5043     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5044         valid++; // allow comments in PV
5045         continue;
5046     }
5047     nr++;
5048     if(endPV+1 > framePtr) break; // no space, truncate
5049     if(!valid) break;
5050     endPV++;
5051     CopyBoard(boards[endPV], boards[endPV-1]);
5052     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5053     moveList[endPV-1][0] = fromX + AAA;
5054     moveList[endPV-1][1] = fromY + ONE;
5055     moveList[endPV-1][2] = toX + AAA;
5056     moveList[endPV-1][3] = toY + ONE;
5057     moveList[endPV-1][4] = promoChar;
5058     moveList[endPV-1][5] = NULLCHAR;
5059     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5060     if(storeComments)
5061         CoordsToAlgebraic(boards[endPV - 1],
5062                              PosFlags(endPV - 1),
5063                              fromY, fromX, toY, toX, promoChar,
5064                              parseList[endPV - 1]);
5065     else
5066         parseList[endPV-1][0] = NULLCHAR;
5067   } while(valid);
5068   currentMove = endPV;
5069   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5070   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5071                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5072   DrawPosition(TRUE, boards[currentMove]);
5073 }
5074
5075 Boolean
5076 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5077 {
5078         int startPV;
5079         char *p;
5080
5081         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5082         lastX = x; lastY = y;
5083         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5084         startPV = index;
5085         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5086         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5087         index = startPV;
5088         do{ while(buf[index] && buf[index] != '\n') index++;
5089         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5090         buf[index] = 0;
5091         ParsePV(buf+startPV, FALSE);
5092         *start = startPV; *end = index-1;
5093         return TRUE;
5094 }
5095
5096 Boolean
5097 LoadPV(int x, int y)
5098 { // called on right mouse click to load PV
5099   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5100   lastX = x; lastY = y;
5101   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5102   return TRUE;
5103 }
5104
5105 void
5106 UnLoadPV()
5107 {
5108   if(endPV < 0) return;
5109   endPV = -1;
5110   currentMove = forwardMostMove;
5111   ClearPremoveHighlights();
5112   DrawPosition(TRUE, boards[currentMove]);
5113 }
5114
5115 void
5116 MovePV(int x, int y, int h)
5117 { // step through PV based on mouse coordinates (called on mouse move)
5118   int margin = h>>3, step = 0, dist;
5119
5120   // we must somehow check if right button is still down (might be released off board!)
5121   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5122   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5123   if(!step) return;
5124   lastX = x; lastY = y;
5125
5126   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5127   if(endPV < 0) return;
5128   if(y < margin) step = 1; else
5129   if(y > h - margin) step = -1;
5130   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5131   currentMove += step;
5132   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5133   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5134                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5135   DrawPosition(FALSE, boards[currentMove]);
5136 }
5137
5138
5139 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5140 // All positions will have equal probability, but the current method will not provide a unique
5141 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5142 #define DARK 1
5143 #define LITE 2
5144 #define ANY 3
5145
5146 int squaresLeft[4];
5147 int piecesLeft[(int)BlackPawn];
5148 int seed, nrOfShuffles;
5149
5150 void GetPositionNumber()
5151 {       // sets global variable seed
5152         int i;
5153
5154         seed = appData.defaultFrcPosition;
5155         if(seed < 0) { // randomize based on time for negative FRC position numbers
5156                 for(i=0; i<50; i++) seed += random();
5157                 seed = random() ^ random() >> 8 ^ random() << 8;
5158                 if(seed<0) seed = -seed;
5159         }
5160 }
5161
5162 int put(Board board, int pieceType, int rank, int n, int shade)
5163 // put the piece on the (n-1)-th empty squares of the given shade
5164 {
5165         int i;
5166
5167         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5168                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5169                         board[rank][i] = (ChessSquare) pieceType;
5170                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5171                         squaresLeft[ANY]--;
5172                         piecesLeft[pieceType]--;
5173                         return i;
5174                 }
5175         }
5176         return -1;
5177 }
5178
5179
5180 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5181 // calculate where the next piece goes, (any empty square), and put it there
5182 {
5183         int i;
5184
5185         i = seed % squaresLeft[shade];
5186         nrOfShuffles *= squaresLeft[shade];
5187         seed /= squaresLeft[shade];
5188         put(board, pieceType, rank, i, shade);
5189 }
5190
5191 void AddTwoPieces(Board board, int pieceType, int rank)
5192 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5193 {
5194         int i, n=squaresLeft[ANY], j=n-1, k;
5195
5196         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5197         i = seed % k;  // pick one
5198         nrOfShuffles *= k;
5199         seed /= k;
5200         while(i >= j) i -= j--;
5201         j = n - 1 - j; i += j;
5202         put(board, pieceType, rank, j, ANY);
5203         put(board, pieceType, rank, i, ANY);
5204 }
5205
5206 void SetUpShuffle(Board board, int number)
5207 {
5208         int i, p, first=1;
5209
5210         GetPositionNumber(); nrOfShuffles = 1;
5211
5212         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5213         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5214         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5215
5216         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5217
5218         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5219             p = (int) board[0][i];
5220             if(p < (int) BlackPawn) piecesLeft[p] ++;
5221             board[0][i] = EmptySquare;
5222         }
5223
5224         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5225             // shuffles restricted to allow normal castling put KRR first
5226             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5227                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5228             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5229                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5230             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5231                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5232             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5233                 put(board, WhiteRook, 0, 0, ANY);
5234             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5235         }
5236
5237         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5238             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5239             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5240                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5241                 while(piecesLeft[p] >= 2) {
5242                     AddOnePiece(board, p, 0, LITE);
5243                     AddOnePiece(board, p, 0, DARK);
5244                 }
5245                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5246             }
5247
5248         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5249             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5250             // but we leave King and Rooks for last, to possibly obey FRC restriction
5251             if(p == (int)WhiteRook) continue;
5252             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5253             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5254         }
5255
5256         // now everything is placed, except perhaps King (Unicorn) and Rooks
5257
5258         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5259             // Last King gets castling rights
5260             while(piecesLeft[(int)WhiteUnicorn]) {
5261                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5262                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5263             }
5264
5265             while(piecesLeft[(int)WhiteKing]) {
5266                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5267                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5268             }
5269
5270
5271         } else {
5272             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5273             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5274         }
5275
5276         // Only Rooks can be left; simply place them all
5277         while(piecesLeft[(int)WhiteRook]) {
5278                 i = put(board, WhiteRook, 0, 0, ANY);
5279                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5280                         if(first) {
5281                                 first=0;
5282                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5283                         }
5284                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5285                 }
5286         }
5287         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5288             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5289         }
5290
5291         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5292 }
5293
5294 int SetCharTable( char *table, const char * map )
5295 /* [HGM] moved here from winboard.c because of its general usefulness */
5296 /*       Basically a safe strcpy that uses the last character as King */
5297 {
5298     int result = FALSE; int NrPieces;
5299
5300     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5301                     && NrPieces >= 12 && !(NrPieces&1)) {
5302         int i; /* [HGM] Accept even length from 12 to 34 */
5303
5304         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5305         for( i=0; i<NrPieces/2-1; i++ ) {
5306             table[i] = map[i];
5307             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5308         }
5309         table[(int) WhiteKing]  = map[NrPieces/2-1];
5310         table[(int) BlackKing]  = map[NrPieces-1];
5311
5312         result = TRUE;
5313     }
5314
5315     return result;
5316 }
5317
5318 void Prelude(Board board)
5319 {       // [HGM] superchess: random selection of exo-pieces
5320         int i, j, k; ChessSquare p;
5321         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5322
5323         GetPositionNumber(); // use FRC position number
5324
5325         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5326             SetCharTable(pieceToChar, appData.pieceToCharTable);
5327             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5328                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5329         }
5330
5331         j = seed%4;                 seed /= 4;
5332         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5333         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5334         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5335         j = seed%3 + (seed%3 >= j); seed /= 3;
5336         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5337         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5338         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5339         j = seed%3;                 seed /= 3;
5340         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5341         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5342         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5343         j = seed%2 + (seed%2 >= j); seed /= 2;
5344         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5345         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5346         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5347         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5348         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5349         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5350         put(board, exoPieces[0],    0, 0, ANY);
5351         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5352 }
5353
5354 void
5355 InitPosition(redraw)
5356      int redraw;
5357 {
5358     ChessSquare (* pieces)[BOARD_FILES];
5359     int i, j, pawnRow, overrule,
5360     oldx = gameInfo.boardWidth,
5361     oldy = gameInfo.boardHeight,
5362     oldh = gameInfo.holdingsWidth;
5363     static int oldv;
5364
5365     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5366
5367     /* [AS] Initialize pv info list [HGM] and game status */
5368     {
5369         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5370             pvInfoList[i].depth = 0;
5371             boards[i][EP_STATUS] = EP_NONE;
5372             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5373         }
5374
5375         initialRulePlies = 0; /* 50-move counter start */
5376
5377         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5378         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5379     }
5380
5381
5382     /* [HGM] logic here is completely changed. In stead of full positions */
5383     /* the initialized data only consist of the two backranks. The switch */
5384     /* selects which one we will use, which is than copied to the Board   */
5385     /* initialPosition, which for the rest is initialized by Pawns and    */
5386     /* empty squares. This initial position is then copied to boards[0],  */
5387     /* possibly after shuffling, so that it remains available.            */
5388
5389     gameInfo.holdingsWidth = 0; /* default board sizes */
5390     gameInfo.boardWidth    = 8;
5391     gameInfo.boardHeight   = 8;
5392     gameInfo.holdingsSize  = 0;
5393     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5394     for(i=0; i<BOARD_FILES-2; i++)
5395       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5396     initialPosition[EP_STATUS] = EP_NONE;
5397     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5398     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5399          SetCharTable(pieceNickName, appData.pieceNickNames);
5400     else SetCharTable(pieceNickName, "............");
5401     pieces = FIDEArray;
5402
5403     switch (gameInfo.variant) {
5404     case VariantFischeRandom:
5405       shuffleOpenings = TRUE;
5406     default:
5407       break;
5408     case VariantShatranj:
5409       pieces = ShatranjArray;
5410       nrCastlingRights = 0;
5411       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5412       break;
5413     case VariantMakruk:
5414       pieces = makrukArray;
5415       nrCastlingRights = 0;
5416       startedFromSetupPosition = TRUE;
5417       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5418       break;
5419     case VariantTwoKings:
5420       pieces = twoKingsArray;
5421       break;
5422     case VariantCapaRandom:
5423       shuffleOpenings = TRUE;
5424     case VariantCapablanca:
5425       pieces = CapablancaArray;
5426       gameInfo.boardWidth = 10;
5427       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5428       break;
5429     case VariantGothic:
5430       pieces = GothicArray;
5431       gameInfo.boardWidth = 10;
5432       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5433       break;
5434     case VariantSChess:
5435       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5436       gameInfo.holdingsSize = 7;
5437       break;
5438     case VariantJanus:
5439       pieces = JanusArray;
5440       gameInfo.boardWidth = 10;
5441       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5442       nrCastlingRights = 6;
5443         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5444         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5445         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5446         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5447         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5448         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5449       break;
5450     case VariantFalcon:
5451       pieces = FalconArray;
5452       gameInfo.boardWidth = 10;
5453       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5454       break;
5455     case VariantXiangqi:
5456       pieces = XiangqiArray;
5457       gameInfo.boardWidth  = 9;
5458       gameInfo.boardHeight = 10;
5459       nrCastlingRights = 0;
5460       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5461       break;
5462     case VariantShogi:
5463       pieces = ShogiArray;
5464       gameInfo.boardWidth  = 9;
5465       gameInfo.boardHeight = 9;
5466       gameInfo.holdingsSize = 7;
5467       nrCastlingRights = 0;
5468       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5469       break;
5470     case VariantCourier:
5471       pieces = CourierArray;
5472       gameInfo.boardWidth  = 12;
5473       nrCastlingRights = 0;
5474       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5475       break;
5476     case VariantKnightmate:
5477       pieces = KnightmateArray;
5478       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5479       break;
5480     case VariantSpartan:
5481       pieces = SpartanArray;
5482       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5483       break;
5484     case VariantFairy:
5485       pieces = fairyArray;
5486       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5487       break;
5488     case VariantGreat:
5489       pieces = GreatArray;
5490       gameInfo.boardWidth = 10;
5491       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5492       gameInfo.holdingsSize = 8;
5493       break;
5494     case VariantSuper:
5495       pieces = FIDEArray;
5496       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5497       gameInfo.holdingsSize = 8;
5498       startedFromSetupPosition = TRUE;
5499       break;
5500     case VariantCrazyhouse:
5501     case VariantBughouse:
5502       pieces = FIDEArray;
5503       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5504       gameInfo.holdingsSize = 5;
5505       break;
5506     case VariantWildCastle:
5507       pieces = FIDEArray;
5508       /* !!?shuffle with kings guaranteed to be on d or e file */
5509       shuffleOpenings = 1;
5510       break;
5511     case VariantNoCastle:
5512       pieces = FIDEArray;
5513       nrCastlingRights = 0;
5514       /* !!?unconstrained back-rank shuffle */
5515       shuffleOpenings = 1;
5516       break;
5517     }
5518
5519     overrule = 0;
5520     if(appData.NrFiles >= 0) {
5521         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5522         gameInfo.boardWidth = appData.NrFiles;
5523     }
5524     if(appData.NrRanks >= 0) {
5525         gameInfo.boardHeight = appData.NrRanks;
5526     }
5527     if(appData.holdingsSize >= 0) {
5528         i = appData.holdingsSize;
5529         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5530         gameInfo.holdingsSize = i;
5531     }
5532     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5533     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5534         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5535
5536     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5537     if(pawnRow < 1) pawnRow = 1;
5538     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5539
5540     /* User pieceToChar list overrules defaults */
5541     if(appData.pieceToCharTable != NULL)
5542         SetCharTable(pieceToChar, appData.pieceToCharTable);
5543
5544     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5545
5546         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5547             s = (ChessSquare) 0; /* account holding counts in guard band */
5548         for( i=0; i<BOARD_HEIGHT; i++ )
5549             initialPosition[i][j] = s;
5550
5551         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5552         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5553         initialPosition[pawnRow][j] = WhitePawn;
5554         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5555         if(gameInfo.variant == VariantXiangqi) {
5556             if(j&1) {
5557                 initialPosition[pawnRow][j] =
5558                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5559                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5560                    initialPosition[2][j] = WhiteCannon;
5561                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5562                 }
5563             }
5564         }
5565         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5566     }
5567     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5568
5569             j=BOARD_LEFT+1;
5570             initialPosition[1][j] = WhiteBishop;
5571             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5572             j=BOARD_RGHT-2;
5573             initialPosition[1][j] = WhiteRook;
5574             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5575     }
5576
5577     if( nrCastlingRights == -1) {
5578         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5579         /*       This sets default castling rights from none to normal corners   */
5580         /* Variants with other castling rights must set them themselves above    */
5581         nrCastlingRights = 6;
5582
5583         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5584         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5585         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5586         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5587         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5588         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5589      }
5590
5591      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5592      if(gameInfo.variant == VariantGreat) { // promotion commoners
5593         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5594         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5595         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5596         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5597      }
5598      if( gameInfo.variant == VariantSChess ) {
5599       initialPosition[1][0] = BlackMarshall;
5600       initialPosition[2][0] = BlackAngel;
5601       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5602       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5603       initialPosition[1][1] = initialPosition[2][1] = 
5604       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5605      }
5606   if (appData.debugMode) {
5607     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5608   }
5609     if(shuffleOpenings) {
5610         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5611         startedFromSetupPosition = TRUE;
5612     }
5613     if(startedFromPositionFile) {
5614       /* [HGM] loadPos: use PositionFile for every new game */
5615       CopyBoard(initialPosition, filePosition);
5616       for(i=0; i<nrCastlingRights; i++)
5617           initialRights[i] = filePosition[CASTLING][i];
5618       startedFromSetupPosition = TRUE;
5619     }
5620
5621     CopyBoard(boards[0], initialPosition);
5622
5623     if(oldx != gameInfo.boardWidth ||
5624        oldy != gameInfo.boardHeight ||
5625        oldv != gameInfo.variant ||
5626        oldh != gameInfo.holdingsWidth
5627                                          )
5628             InitDrawingSizes(-2 ,0);
5629
5630     oldv = gameInfo.variant;
5631     if (redraw)
5632       DrawPosition(TRUE, boards[currentMove]);
5633 }
5634
5635 void
5636 SendBoard(cps, moveNum)
5637      ChessProgramState *cps;
5638      int moveNum;
5639 {
5640     char message[MSG_SIZ];
5641
5642     if (cps->useSetboard) {
5643       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5644       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5645       SendToProgram(message, cps);
5646       free(fen);
5647
5648     } else {
5649       ChessSquare *bp;
5650       int i, j;
5651       /* Kludge to set black to move, avoiding the troublesome and now
5652        * deprecated "black" command.
5653        */
5654       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5655         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5656
5657       SendToProgram("edit\n", cps);
5658       SendToProgram("#\n", cps);
5659       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5660         bp = &boards[moveNum][i][BOARD_LEFT];
5661         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5662           if ((int) *bp < (int) BlackPawn) {
5663             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5664                     AAA + j, ONE + i);
5665             if(message[0] == '+' || message[0] == '~') {
5666               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5667                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5668                         AAA + j, ONE + i);
5669             }
5670             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5671                 message[1] = BOARD_RGHT   - 1 - j + '1';
5672                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5673             }
5674             SendToProgram(message, cps);
5675           }
5676         }
5677       }
5678
5679       SendToProgram("c\n", cps);
5680       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5681         bp = &boards[moveNum][i][BOARD_LEFT];
5682         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5683           if (((int) *bp != (int) EmptySquare)
5684               && ((int) *bp >= (int) BlackPawn)) {
5685             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5686                     AAA + j, ONE + i);
5687             if(message[0] == '+' || message[0] == '~') {
5688               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5689                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5690                         AAA + j, ONE + i);
5691             }
5692             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5693                 message[1] = BOARD_RGHT   - 1 - j + '1';
5694                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5695             }
5696             SendToProgram(message, cps);
5697           }
5698         }
5699       }
5700
5701       SendToProgram(".\n", cps);
5702     }
5703     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5704 }
5705
5706 static int autoQueen; // [HGM] oneclick
5707
5708 int
5709 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5710 {
5711     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5712     /* [HGM] add Shogi promotions */
5713     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5714     ChessSquare piece;
5715     ChessMove moveType;
5716     Boolean premove;
5717
5718     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5719     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5720
5721     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5722       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5723         return FALSE;
5724
5725     piece = boards[currentMove][fromY][fromX];
5726     if(gameInfo.variant == VariantShogi) {
5727         promotionZoneSize = BOARD_HEIGHT/3;
5728         highestPromotingPiece = (int)WhiteFerz;
5729     } else if(gameInfo.variant == VariantMakruk) {
5730         promotionZoneSize = 3;
5731     }
5732
5733     // Treat Lance as Pawn when it is not representing Amazon
5734     if(gameInfo.variant != VariantSuper) {
5735         if(piece == WhiteLance) piece = WhitePawn; else
5736         if(piece == BlackLance) piece = BlackPawn;
5737     }
5738
5739     // next weed out all moves that do not touch the promotion zone at all
5740     if((int)piece >= BlackPawn) {
5741         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5742              return FALSE;
5743         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5744     } else {
5745         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5746            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5747     }
5748
5749     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5750
5751     // weed out mandatory Shogi promotions
5752     if(gameInfo.variant == VariantShogi) {
5753         if(piece >= BlackPawn) {
5754             if(toY == 0 && piece == BlackPawn ||
5755                toY == 0 && piece == BlackQueen ||
5756                toY <= 1 && piece == BlackKnight) {
5757                 *promoChoice = '+';
5758                 return FALSE;
5759             }
5760         } else {
5761             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5762                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5763                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5764                 *promoChoice = '+';
5765                 return FALSE;
5766             }
5767         }
5768     }
5769
5770     // weed out obviously illegal Pawn moves
5771     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5772         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5773         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5774         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5775         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5776         // note we are not allowed to test for valid (non-)capture, due to premove
5777     }
5778
5779     // we either have a choice what to promote to, or (in Shogi) whether to promote
5780     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5781         *promoChoice = PieceToChar(BlackFerz);  // no choice
5782         return FALSE;
5783     }
5784     // no sense asking what we must promote to if it is going to explode...
5785     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5786         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5787         return FALSE;
5788     }
5789     // give caller the default choice even if we will not make it
5790     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5791          *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5792     else if(gameInfo.variant == VariantSpartan)
5793          *promoChoice = ToLower(PieceToChar(toY ? WhiteQueen : BlackAngel));
5794     else if(gameInfo.variant == VariantShogi)
5795          *promoChoice = '+';
5796     else *promoChoice = PieceToChar(BlackQueen);
5797     if(autoQueen) return FALSE; // predetermined
5798
5799     // suppress promotion popup on illegal moves that are not premoves
5800     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5801               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5802     if(appData.testLegality && !premove) {
5803         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5804                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5805         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5806             return FALSE;
5807     }
5808
5809     return TRUE;
5810 }
5811
5812 int
5813 InPalace(row, column)
5814      int row, column;
5815 {   /* [HGM] for Xiangqi */
5816     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5817          column < (BOARD_WIDTH + 4)/2 &&
5818          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5819     return FALSE;
5820 }
5821
5822 int
5823 PieceForSquare (x, y)
5824      int x;
5825      int y;
5826 {
5827   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5828      return -1;
5829   else
5830      return boards[currentMove][y][x];
5831 }
5832
5833 int
5834 OKToStartUserMove(x, y)
5835      int x, y;
5836 {
5837     ChessSquare from_piece;
5838     int white_piece;
5839
5840     if (matchMode) return FALSE;
5841     if (gameMode == EditPosition) return TRUE;
5842
5843     if (x >= 0 && y >= 0)
5844       from_piece = boards[currentMove][y][x];
5845     else
5846       from_piece = EmptySquare;
5847
5848     if (from_piece == EmptySquare) return FALSE;
5849
5850     white_piece = (int)from_piece >= (int)WhitePawn &&
5851       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5852
5853     switch (gameMode) {
5854       case PlayFromGameFile:
5855       case AnalyzeFile:
5856       case TwoMachinesPlay:
5857       case EndOfGame:
5858         return FALSE;
5859
5860       case IcsObserving:
5861       case IcsIdle:
5862         return FALSE;
5863
5864       case MachinePlaysWhite:
5865       case IcsPlayingBlack:
5866         if (appData.zippyPlay) return FALSE;
5867         if (white_piece) {
5868             DisplayMoveError(_("You are playing Black"));
5869             return FALSE;
5870         }
5871         break;
5872
5873       case MachinePlaysBlack:
5874       case IcsPlayingWhite:
5875         if (appData.zippyPlay) return FALSE;
5876         if (!white_piece) {
5877             DisplayMoveError(_("You are playing White"));
5878             return FALSE;
5879         }
5880         break;
5881
5882       case EditGame:
5883         if (!white_piece && WhiteOnMove(currentMove)) {
5884             DisplayMoveError(_("It is White's turn"));
5885             return FALSE;
5886         }
5887         if (white_piece && !WhiteOnMove(currentMove)) {
5888             DisplayMoveError(_("It is Black's turn"));
5889             return FALSE;
5890         }
5891         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5892             /* Editing correspondence game history */
5893             /* Could disallow this or prompt for confirmation */
5894             cmailOldMove = -1;
5895         }
5896         break;
5897
5898       case BeginningOfGame:
5899         if (appData.icsActive) return FALSE;
5900         if (!appData.noChessProgram) {
5901             if (!white_piece) {
5902                 DisplayMoveError(_("You are playing White"));
5903                 return FALSE;
5904             }
5905         }
5906         break;
5907
5908       case Training:
5909         if (!white_piece && WhiteOnMove(currentMove)) {
5910             DisplayMoveError(_("It is White's turn"));
5911             return FALSE;
5912         }
5913         if (white_piece && !WhiteOnMove(currentMove)) {
5914             DisplayMoveError(_("It is Black's turn"));
5915             return FALSE;
5916         }
5917         break;
5918
5919       default:
5920       case IcsExamining:
5921         break;
5922     }
5923     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5924         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5925         && gameMode != AnalyzeFile && gameMode != Training) {
5926         DisplayMoveError(_("Displayed position is not current"));
5927         return FALSE;
5928     }
5929     return TRUE;
5930 }
5931
5932 Boolean
5933 OnlyMove(int *x, int *y, Boolean captures) {
5934     DisambiguateClosure cl;
5935     if (appData.zippyPlay) return FALSE;
5936     switch(gameMode) {
5937       case MachinePlaysBlack:
5938       case IcsPlayingWhite:
5939       case BeginningOfGame:
5940         if(!WhiteOnMove(currentMove)) return FALSE;
5941         break;
5942       case MachinePlaysWhite:
5943       case IcsPlayingBlack:
5944         if(WhiteOnMove(currentMove)) return FALSE;
5945         break;
5946       case EditGame:
5947         break;
5948       default:
5949         return FALSE;
5950     }
5951     cl.pieceIn = EmptySquare;
5952     cl.rfIn = *y;
5953     cl.ffIn = *x;
5954     cl.rtIn = -1;
5955     cl.ftIn = -1;
5956     cl.promoCharIn = NULLCHAR;
5957     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5958     if( cl.kind == NormalMove ||
5959         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5960         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5961         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5962       fromX = cl.ff;
5963       fromY = cl.rf;
5964       *x = cl.ft;
5965       *y = cl.rt;
5966       return TRUE;
5967     }
5968     if(cl.kind != ImpossibleMove) return FALSE;
5969     cl.pieceIn = EmptySquare;
5970     cl.rfIn = -1;
5971     cl.ffIn = -1;
5972     cl.rtIn = *y;
5973     cl.ftIn = *x;
5974     cl.promoCharIn = NULLCHAR;
5975     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5976     if( cl.kind == NormalMove ||
5977         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5978         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5979         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5980       fromX = cl.ff;
5981       fromY = cl.rf;
5982       *x = cl.ft;
5983       *y = cl.rt;
5984       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5985       return TRUE;
5986     }
5987     return FALSE;
5988 }
5989
5990 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5991 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5992 int lastLoadGameUseList = FALSE;
5993 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5994 ChessMove lastLoadGameStart = EndOfFile;
5995
5996 void
5997 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5998      int fromX, fromY, toX, toY;
5999      int promoChar;
6000 {
6001     ChessMove moveType;
6002     ChessSquare pdown, pup;
6003
6004     /* Check if the user is playing in turn.  This is complicated because we
6005        let the user "pick up" a piece before it is his turn.  So the piece he
6006        tried to pick up may have been captured by the time he puts it down!
6007        Therefore we use the color the user is supposed to be playing in this
6008        test, not the color of the piece that is currently on the starting
6009        square---except in EditGame mode, where the user is playing both
6010        sides; fortunately there the capture race can't happen.  (It can
6011        now happen in IcsExamining mode, but that's just too bad.  The user
6012        will get a somewhat confusing message in that case.)
6013        */
6014
6015     switch (gameMode) {
6016       case PlayFromGameFile:
6017       case AnalyzeFile:
6018       case TwoMachinesPlay:
6019       case EndOfGame:
6020       case IcsObserving:
6021       case IcsIdle:
6022         /* We switched into a game mode where moves are not accepted,
6023            perhaps while the mouse button was down. */
6024         return;
6025
6026       case MachinePlaysWhite:
6027         /* User is moving for Black */
6028         if (WhiteOnMove(currentMove)) {
6029             DisplayMoveError(_("It is White's turn"));
6030             return;
6031         }
6032         break;
6033
6034       case MachinePlaysBlack:
6035         /* User is moving for White */
6036         if (!WhiteOnMove(currentMove)) {
6037             DisplayMoveError(_("It is Black's turn"));
6038             return;
6039         }
6040         break;
6041
6042       case EditGame:
6043       case IcsExamining:
6044       case BeginningOfGame:
6045       case AnalyzeMode:
6046       case Training:
6047         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6048         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6049             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6050             /* User is moving for Black */
6051             if (WhiteOnMove(currentMove)) {
6052                 DisplayMoveError(_("It is White's turn"));
6053                 return;
6054             }
6055         } else {
6056             /* User is moving for White */
6057             if (!WhiteOnMove(currentMove)) {
6058                 DisplayMoveError(_("It is Black's turn"));
6059                 return;
6060             }
6061         }
6062         break;
6063
6064       case IcsPlayingBlack:
6065         /* User is moving for Black */
6066         if (WhiteOnMove(currentMove)) {
6067             if (!appData.premove) {
6068                 DisplayMoveError(_("It is White's turn"));
6069             } else if (toX >= 0 && toY >= 0) {
6070                 premoveToX = toX;
6071                 premoveToY = toY;
6072                 premoveFromX = fromX;
6073                 premoveFromY = fromY;
6074                 premovePromoChar = promoChar;
6075                 gotPremove = 1;
6076                 if (appData.debugMode)
6077                     fprintf(debugFP, "Got premove: fromX %d,"
6078                             "fromY %d, toX %d, toY %d\n",
6079                             fromX, fromY, toX, toY);
6080             }
6081             return;
6082         }
6083         break;
6084
6085       case IcsPlayingWhite:
6086         /* User is moving for White */
6087         if (!WhiteOnMove(currentMove)) {
6088             if (!appData.premove) {
6089                 DisplayMoveError(_("It is Black's turn"));
6090             } else if (toX >= 0 && toY >= 0) {
6091                 premoveToX = toX;
6092                 premoveToY = toY;
6093                 premoveFromX = fromX;
6094                 premoveFromY = fromY;
6095                 premovePromoChar = promoChar;
6096                 gotPremove = 1;
6097                 if (appData.debugMode)
6098                     fprintf(debugFP, "Got premove: fromX %d,"
6099                             "fromY %d, toX %d, toY %d\n",
6100                             fromX, fromY, toX, toY);
6101             }
6102             return;
6103         }
6104         break;
6105
6106       default:
6107         break;
6108
6109       case EditPosition:
6110         /* EditPosition, empty square, or different color piece;
6111            click-click move is possible */
6112         if (toX == -2 || toY == -2) {
6113             boards[0][fromY][fromX] = EmptySquare;
6114             DrawPosition(FALSE, boards[currentMove]);
6115             return;
6116         } else if (toX >= 0 && toY >= 0) {
6117             boards[0][toY][toX] = boards[0][fromY][fromX];
6118             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6119                 if(boards[0][fromY][0] != EmptySquare) {
6120                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6121                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6122                 }
6123             } else
6124             if(fromX == BOARD_RGHT+1) {
6125                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6126                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6127                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6128                 }
6129             } else
6130             boards[0][fromY][fromX] = EmptySquare;
6131             DrawPosition(FALSE, boards[currentMove]);
6132             return;
6133         }
6134         return;
6135     }
6136
6137     if(toX < 0 || toY < 0) return;
6138     pdown = boards[currentMove][fromY][fromX];
6139     pup = boards[currentMove][toY][toX];
6140
6141     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6142     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6143          if( pup != EmptySquare ) return;
6144          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6145            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6146                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6147            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6148            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6149            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6150            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6151          fromY = DROP_RANK;
6152     }
6153
6154     /* [HGM] always test for legality, to get promotion info */
6155     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6156                                          fromY, fromX, toY, toX, promoChar);
6157     /* [HGM] but possibly ignore an IllegalMove result */
6158     if (appData.testLegality) {
6159         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6160             DisplayMoveError(_("Illegal move"));
6161             return;
6162         }
6163     }
6164
6165     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6166 }
6167
6168 /* Common tail of UserMoveEvent and DropMenuEvent */
6169 int
6170 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6171      ChessMove moveType;
6172      int fromX, fromY, toX, toY;
6173      /*char*/int promoChar;
6174 {
6175     char *bookHit = 0;
6176
6177     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6178         // [HGM] superchess: suppress promotions to non-available piece
6179         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6180         if(WhiteOnMove(currentMove)) {
6181             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6182         } else {
6183             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6184         }
6185     }
6186
6187     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6188        move type in caller when we know the move is a legal promotion */
6189     if(moveType == NormalMove && promoChar)
6190         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6191
6192     /* [HGM] <popupFix> The following if has been moved here from
6193        UserMoveEvent(). Because it seemed to belong here (why not allow
6194        piece drops in training games?), and because it can only be
6195        performed after it is known to what we promote. */
6196     if (gameMode == Training) {
6197       /* compare the move played on the board to the next move in the
6198        * game. If they match, display the move and the opponent's response.
6199        * If they don't match, display an error message.
6200        */
6201       int saveAnimate;
6202       Board testBoard;
6203       CopyBoard(testBoard, boards[currentMove]);
6204       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6205
6206       if (CompareBoards(testBoard, boards[currentMove+1])) {
6207         ForwardInner(currentMove+1);
6208
6209         /* Autoplay the opponent's response.
6210          * if appData.animate was TRUE when Training mode was entered,
6211          * the response will be animated.
6212          */
6213         saveAnimate = appData.animate;
6214         appData.animate = animateTraining;
6215         ForwardInner(currentMove+1);
6216         appData.animate = saveAnimate;
6217
6218         /* check for the end of the game */
6219         if (currentMove >= forwardMostMove) {
6220           gameMode = PlayFromGameFile;
6221           ModeHighlight();
6222           SetTrainingModeOff();
6223           DisplayInformation(_("End of game"));
6224         }
6225       } else {
6226         DisplayError(_("Incorrect move"), 0);
6227       }
6228       return 1;
6229     }
6230
6231   /* Ok, now we know that the move is good, so we can kill
6232      the previous line in Analysis Mode */
6233   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6234                                 && currentMove < forwardMostMove) {
6235     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6236     else forwardMostMove = currentMove;
6237   }
6238
6239   /* If we need the chess program but it's dead, restart it */
6240   ResurrectChessProgram();
6241
6242   /* A user move restarts a paused game*/
6243   if (pausing)
6244     PauseEvent();
6245
6246   thinkOutput[0] = NULLCHAR;
6247
6248   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6249
6250   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6251     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6252     return 1;
6253   }
6254
6255   if (gameMode == BeginningOfGame) {
6256     if (appData.noChessProgram) {
6257       gameMode = EditGame;
6258       SetGameInfo();
6259     } else {
6260       char buf[MSG_SIZ];
6261       gameMode = MachinePlaysBlack;
6262       StartClocks();
6263       SetGameInfo();
6264       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6265       DisplayTitle(buf);
6266       if (first.sendName) {
6267         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6268         SendToProgram(buf, &first);
6269       }
6270       StartClocks();
6271     }
6272     ModeHighlight();
6273   }
6274
6275   /* Relay move to ICS or chess engine */
6276   if (appData.icsActive) {
6277     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6278         gameMode == IcsExamining) {
6279       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6280         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6281         SendToICS("draw ");
6282         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6283       }
6284       // also send plain move, in case ICS does not understand atomic claims
6285       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6286       ics_user_moved = 1;
6287     }
6288   } else {
6289     if (first.sendTime && (gameMode == BeginningOfGame ||
6290                            gameMode == MachinePlaysWhite ||
6291                            gameMode == MachinePlaysBlack)) {
6292       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6293     }
6294     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6295          // [HGM] book: if program might be playing, let it use book
6296         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6297         first.maybeThinking = TRUE;
6298     } else SendMoveToProgram(forwardMostMove-1, &first);
6299     if (currentMove == cmailOldMove + 1) {
6300       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6301     }
6302   }
6303
6304   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6305
6306   switch (gameMode) {
6307   case EditGame:
6308     if(appData.testLegality)
6309     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6310     case MT_NONE:
6311     case MT_CHECK:
6312       break;
6313     case MT_CHECKMATE:
6314     case MT_STAINMATE:
6315       if (WhiteOnMove(currentMove)) {
6316         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6317       } else {
6318         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6319       }
6320       break;
6321     case MT_STALEMATE:
6322       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6323       break;
6324     }
6325     break;
6326
6327   case MachinePlaysBlack:
6328   case MachinePlaysWhite:
6329     /* disable certain menu options while machine is thinking */
6330     SetMachineThinkingEnables();
6331     break;
6332
6333   default:
6334     break;
6335   }
6336
6337   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6338
6339   if(bookHit) { // [HGM] book: simulate book reply
6340         static char bookMove[MSG_SIZ]; // a bit generous?
6341
6342         programStats.nodes = programStats.depth = programStats.time =
6343         programStats.score = programStats.got_only_move = 0;
6344         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6345
6346         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6347         strcat(bookMove, bookHit);
6348         HandleMachineMove(bookMove, &first);
6349   }
6350   return 1;
6351 }
6352
6353 void
6354 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6355      Board board;
6356      int flags;
6357      ChessMove kind;
6358      int rf, ff, rt, ft;
6359      VOIDSTAR closure;
6360 {
6361     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6362     Markers *m = (Markers *) closure;
6363     if(rf == fromY && ff == fromX)
6364         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6365                          || kind == WhiteCapturesEnPassant
6366                          || kind == BlackCapturesEnPassant);
6367     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6368 }
6369
6370 void
6371 MarkTargetSquares(int clear)
6372 {
6373   int x, y;
6374   if(!appData.markers || !appData.highlightDragging ||
6375      !appData.testLegality || gameMode == EditPosition) return;
6376   if(clear) {
6377     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6378   } else {
6379     int capt = 0;
6380     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6381     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6382       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6383       if(capt)
6384       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6385     }
6386   }
6387   DrawPosition(TRUE, NULL);
6388 }
6389
6390 int
6391 Explode(Board board, int fromX, int fromY, int toX, int toY)
6392 {
6393     if(gameInfo.variant == VariantAtomic &&
6394        (board[toY][toX] != EmptySquare ||                     // capture?
6395         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6396                          board[fromY][fromX] == BlackPawn   )
6397       )) {
6398         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6399         return TRUE;
6400     }
6401     return FALSE;
6402 }
6403
6404 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6405
6406 void LeftClick(ClickType clickType, int xPix, int yPix)
6407 {
6408     int x, y;
6409     Boolean saveAnimate;
6410     static int second = 0, promotionChoice = 0, dragging = 0;
6411     char promoChoice = NULLCHAR;
6412
6413     if(appData.seekGraph && appData.icsActive && loggedOn &&
6414         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6415         SeekGraphClick(clickType, xPix, yPix, 0);
6416         return;
6417     }
6418
6419     if (clickType == Press) ErrorPopDown();
6420     MarkTargetSquares(1);
6421
6422     x = EventToSquare(xPix, BOARD_WIDTH);
6423     y = EventToSquare(yPix, BOARD_HEIGHT);
6424     if (!flipView && y >= 0) {
6425         y = BOARD_HEIGHT - 1 - y;
6426     }
6427     if (flipView && x >= 0) {
6428         x = BOARD_WIDTH - 1 - x;
6429     }
6430
6431     if(promoSweep != EmptySquare) {
6432         char promoChar = ToLower(PieceToChar(promoSweep));
6433         if(gameInfo.variant == VariantShogi) promoChar = promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+';
6434         saveAnimate = appData.animate; appData.animate = FALSE;
6435         UserMoveEvent(fromX, fromY, toX, toY, promoChar);
6436         appData.animate = saveAnimate;
6437         promoSweep = EmptySquare;
6438         DrawPosition(FALSE, boards[currentMove]);
6439         fromX = fromY = -1;
6440         return;
6441     }
6442
6443     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6444         if(clickType == Release) return; // ignore upclick of click-click destination
6445         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6446         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6447         if(gameInfo.holdingsWidth &&
6448                 (WhiteOnMove(currentMove)
6449                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6450                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6451             // click in right holdings, for determining promotion piece
6452             ChessSquare p = boards[currentMove][y][x];
6453             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6454             if(p != EmptySquare) {
6455                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6456                 fromX = fromY = -1;
6457                 return;
6458             }
6459         }
6460         DrawPosition(FALSE, boards[currentMove]);
6461         return;
6462     }
6463
6464     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6465     if(clickType == Press
6466             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6467               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6468               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6469         return;
6470
6471     autoQueen = appData.alwaysPromoteToQueen;
6472
6473     if (fromX == -1) {
6474       gatingPiece = EmptySquare;
6475       if (clickType != Press) {
6476         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6477             DragPieceEnd(xPix, yPix); dragging = 0;
6478             DrawPosition(FALSE, NULL);
6479         }
6480         return;
6481       }
6482       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6483             /* First square */
6484             if (OKToStartUserMove(x, y)) {
6485                 fromX = x;
6486                 fromY = y;
6487                 second = 0;
6488                 MarkTargetSquares(0);
6489                 DragPieceBegin(xPix, yPix); dragging = 1;
6490                 if (appData.highlightDragging) {
6491                     SetHighlights(x, y, -1, -1);
6492                 }
6493             }
6494             return;
6495         }
6496     }
6497
6498     /* fromX != -1 */
6499     if (clickType == Press && gameMode != EditPosition) {
6500         ChessSquare fromP;
6501         ChessSquare toP;
6502         int frc;
6503
6504         // ignore off-board to clicks
6505         if(y < 0 || x < 0) return;
6506
6507         /* Check if clicking again on the same color piece */
6508         fromP = boards[currentMove][fromY][fromX];
6509         toP = boards[currentMove][y][x];
6510         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6511         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6512              WhitePawn <= toP && toP <= WhiteKing &&
6513              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6514              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6515             (BlackPawn <= fromP && fromP <= BlackKing &&
6516              BlackPawn <= toP && toP <= BlackKing &&
6517              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6518              !(fromP == BlackKing && toP == BlackRook && frc))) {
6519             /* Clicked again on same color piece -- changed his mind */
6520             second = (x == fromX && y == fromY);
6521            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6522             if (appData.highlightDragging) {
6523                 SetHighlights(x, y, -1, -1);
6524             } else {
6525                 ClearHighlights();
6526             }
6527             if (OKToStartUserMove(x, y)) {
6528                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6529                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6530                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6531                  gatingPiece = boards[currentMove][fromY][fromX];
6532                 else gatingPiece = EmptySquare;
6533                 fromX = x;
6534                 fromY = y; dragging = 1;
6535                 MarkTargetSquares(0);
6536                 DragPieceBegin(xPix, yPix);
6537             }
6538            }
6539            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6540            second = FALSE; 
6541         }
6542         // ignore clicks on holdings
6543         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6544     }
6545
6546     if (clickType == Release && x == fromX && y == fromY) {
6547         DragPieceEnd(xPix, yPix); dragging = 0;
6548         if (appData.animateDragging) {
6549             /* Undo animation damage if any */
6550             DrawPosition(FALSE, NULL);
6551         }
6552         if (second) {
6553             /* Second up/down in same square; just abort move */
6554             second = 0;
6555             fromX = fromY = -1;
6556             gatingPiece = EmptySquare;
6557             ClearHighlights();
6558             gotPremove = 0;
6559             ClearPremoveHighlights();
6560         } else {
6561             /* First upclick in same square; start click-click mode */
6562             SetHighlights(x, y, -1, -1);
6563         }
6564         return;
6565     }
6566
6567     /* we now have a different from- and (possibly off-board) to-square */
6568     /* Completed move */
6569     toX = x;
6570     toY = y;
6571     saveAnimate = appData.animate;
6572     if (clickType == Press) {
6573         /* Finish clickclick move */
6574         if (appData.animate || appData.highlightLastMove) {
6575             SetHighlights(fromX, fromY, toX, toY);
6576         } else {
6577             ClearHighlights();
6578         }
6579     } else {
6580         /* Finish drag move */
6581         if (appData.highlightLastMove) {
6582             SetHighlights(fromX, fromY, toX, toY);
6583         } else {
6584             ClearHighlights();
6585         }
6586         DragPieceEnd(xPix, yPix); dragging = 0;
6587         /* Don't animate move and drag both */
6588         appData.animate = FALSE;
6589     }
6590
6591     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6592     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6593         ChessSquare piece = boards[currentMove][fromY][fromX];
6594         if(gameMode == EditPosition && piece != EmptySquare &&
6595            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6596             int n;
6597
6598             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6599                 n = PieceToNumber(piece - (int)BlackPawn);
6600                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6601                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6602                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6603             } else
6604             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6605                 n = PieceToNumber(piece);
6606                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6607                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6608                 boards[currentMove][n][BOARD_WIDTH-2]++;
6609             }
6610             boards[currentMove][fromY][fromX] = EmptySquare;
6611         }
6612         ClearHighlights();
6613         fromX = fromY = -1;
6614         DrawPosition(TRUE, boards[currentMove]);
6615         return;
6616     }
6617
6618     // off-board moves should not be highlighted
6619     if(x < 0 || y < 0) ClearHighlights();
6620
6621     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6622
6623     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6624         SetHighlights(fromX, fromY, toX, toY);
6625         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6626             // [HGM] super: promotion to captured piece selected from holdings
6627             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6628             promotionChoice = TRUE;
6629             // kludge follows to temporarily execute move on display, without promoting yet
6630             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6631             boards[currentMove][toY][toX] = p;
6632             DrawPosition(FALSE, boards[currentMove]);
6633             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6634             boards[currentMove][toY][toX] = q;
6635             DisplayMessage("Click in holdings to choose piece", "");
6636             return;
6637         }
6638         if(appData.sweepSelect && clickType == Press) {
6639              lastX = xPix; lastY = yPix;
6640              ChessSquare piece = boards[currentMove][fromY][fromX];
6641              promoSweep = CharToPiece((piece >= BlackPawn ? ToLower : ToUpper)(promoChoice));
6642              if(promoChoice == '+') promoSweep = PROMOTED piece;
6643              Sweep(0);
6644         } else PromotionPopUp();
6645     } else {
6646         int oldMove = currentMove;
6647         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6648         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6649         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6650         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6651            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6652             DrawPosition(TRUE, boards[currentMove]);
6653         fromX = fromY = -1;
6654     }
6655     appData.animate = saveAnimate;
6656     if (appData.animate || appData.animateDragging) {
6657         /* Undo animation damage if needed */
6658         DrawPosition(FALSE, NULL);
6659     }
6660 }
6661
6662 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6663 {   // front-end-free part taken out of PieceMenuPopup
6664     int whichMenu; int xSqr, ySqr;
6665
6666     if(seekGraphUp) { // [HGM] seekgraph
6667         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6668         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6669         return -2;
6670     }
6671
6672     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6673          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6674         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6675         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6676         if(action == Press)   {
6677             originalFlip = flipView;
6678             flipView = !flipView; // temporarily flip board to see game from partners perspective
6679             DrawPosition(TRUE, partnerBoard);
6680             DisplayMessage(partnerStatus, "");
6681             partnerUp = TRUE;
6682         } else if(action == Release) {
6683             flipView = originalFlip;
6684             DrawPosition(TRUE, boards[currentMove]);
6685             partnerUp = FALSE;
6686         }
6687         return -2;
6688     }
6689
6690     xSqr = EventToSquare(x, BOARD_WIDTH);
6691     ySqr = EventToSquare(y, BOARD_HEIGHT);
6692     if (action == Release) {
6693         if(pieceSweep != EmptySquare) {
6694             EditPositionMenuEvent(pieceSweep, toX, toY);
6695             pieceSweep = EmptySquare;
6696         } else UnLoadPV(); // [HGM] pv
6697     }
6698     if (action != Press) return -2; // return code to be ignored
6699     switch (gameMode) {
6700       case IcsExamining:
6701         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6702       case EditPosition:
6703         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6704         if (xSqr < 0 || ySqr < 0) return -1;
6705         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6706         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6707         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6708         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6709         NextPiece(0);
6710         return -2;\r
6711       case IcsObserving:
6712         if(!appData.icsEngineAnalyze) return -1;
6713       case IcsPlayingWhite:
6714       case IcsPlayingBlack:
6715         if(!appData.zippyPlay) goto noZip;
6716       case AnalyzeMode:
6717       case AnalyzeFile:
6718       case MachinePlaysWhite:
6719       case MachinePlaysBlack:
6720       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6721         if (!appData.dropMenu) {
6722           LoadPV(x, y);
6723           return 2; // flag front-end to grab mouse events
6724         }
6725         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6726            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6727       case EditGame:
6728       noZip:
6729         if (xSqr < 0 || ySqr < 0) return -1;
6730         if (!appData.dropMenu || appData.testLegality &&
6731             gameInfo.variant != VariantBughouse &&
6732             gameInfo.variant != VariantCrazyhouse) return -1;
6733         whichMenu = 1; // drop menu
6734         break;
6735       default:
6736         return -1;
6737     }
6738
6739     if (((*fromX = xSqr) < 0) ||
6740         ((*fromY = ySqr) < 0)) {
6741         *fromX = *fromY = -1;
6742         return -1;
6743     }
6744     if (flipView)
6745       *fromX = BOARD_WIDTH - 1 - *fromX;
6746     else
6747       *fromY = BOARD_HEIGHT - 1 - *fromY;
6748
6749     return whichMenu;
6750 }
6751
6752 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6753 {
6754 //    char * hint = lastHint;
6755     FrontEndProgramStats stats;
6756
6757     stats.which = cps == &first ? 0 : 1;
6758     stats.depth = cpstats->depth;
6759     stats.nodes = cpstats->nodes;
6760     stats.score = cpstats->score;
6761     stats.time = cpstats->time;
6762     stats.pv = cpstats->movelist;
6763     stats.hint = lastHint;
6764     stats.an_move_index = 0;
6765     stats.an_move_count = 0;
6766
6767     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6768         stats.hint = cpstats->move_name;
6769         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6770         stats.an_move_count = cpstats->nr_moves;
6771     }
6772
6773     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
6774
6775     SetProgramStats( &stats );
6776 }
6777
6778 void
6779 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6780 {       // count all piece types
6781         int p, f, r;
6782         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6783         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6784         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6785                 p = board[r][f];
6786                 pCnt[p]++;
6787                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6788                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6789                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6790                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6791                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6792                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6793         }
6794 }
6795
6796 int
6797 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6798 {
6799         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6800         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6801
6802         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6803         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6804         if(myPawns == 2 && nMine == 3) // KPP
6805             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6806         if(myPawns == 1 && nMine == 2) // KP
6807             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6808         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6809             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6810         if(myPawns) return FALSE;
6811         if(pCnt[WhiteRook+side])
6812             return pCnt[BlackRook-side] ||
6813                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6814                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6815                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6816         if(pCnt[WhiteCannon+side]) {
6817             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6818             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6819         }
6820         if(pCnt[WhiteKnight+side])
6821             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6822         return FALSE;
6823 }
6824
6825 int
6826 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6827 {
6828         VariantClass v = gameInfo.variant;
6829
6830         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6831         if(v == VariantShatranj) return TRUE; // always winnable through baring
6832         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6833         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6834
6835         if(v == VariantXiangqi) {
6836                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6837
6838                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6839                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6840                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6841                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6842                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6843                 if(stale) // we have at least one last-rank P plus perhaps C
6844                     return majors // KPKX
6845                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6846                 else // KCA*E*
6847                     return pCnt[WhiteFerz+side] // KCAK
6848                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6849                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6850                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6851
6852         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6853                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6854
6855                 if(nMine == 1) return FALSE; // bare King
6856                 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
6857                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6858                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6859                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6860                 if(pCnt[WhiteKnight+side])
6861                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6862                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6863                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6864                 if(nBishops)
6865                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6866                 if(pCnt[WhiteAlfil+side])
6867                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6868                 if(pCnt[WhiteWazir+side])
6869                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6870         }
6871
6872         return TRUE;
6873 }
6874
6875 int
6876 Adjudicate(ChessProgramState *cps)
6877 {       // [HGM] some adjudications useful with buggy engines
6878         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6879         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6880         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6881         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6882         int k, count = 0; static int bare = 1;
6883         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6884         Boolean canAdjudicate = !appData.icsActive;
6885
6886         // most tests only when we understand the game, i.e. legality-checking on
6887             if( appData.testLegality )
6888             {   /* [HGM] Some more adjudications for obstinate engines */
6889                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6890                 static int moveCount = 6;
6891                 ChessMove result;
6892                 char *reason = NULL;
6893
6894                 /* Count what is on board. */
6895                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6896
6897                 /* Some material-based adjudications that have to be made before stalemate test */
6898                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6899                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6900                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6901                      if(canAdjudicate && appData.checkMates) {
6902                          if(engineOpponent)
6903                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6904                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6905                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6906                          return 1;
6907                      }
6908                 }
6909
6910                 /* Bare King in Shatranj (loses) or Losers (wins) */
6911                 if( nrW == 1 || nrB == 1) {
6912                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6913                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6914                      if(canAdjudicate && appData.checkMates) {
6915                          if(engineOpponent)
6916                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6917                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6918                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6919                          return 1;
6920                      }
6921                   } else
6922                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6923                   {    /* bare King */
6924                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6925                         if(canAdjudicate && appData.checkMates) {
6926                             /* but only adjudicate if adjudication enabled */
6927                             if(engineOpponent)
6928                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6929                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6930                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6931                             return 1;
6932                         }
6933                   }
6934                 } else bare = 1;
6935
6936
6937             // don't wait for engine to announce game end if we can judge ourselves
6938             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6939               case MT_CHECK:
6940                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6941                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6942                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6943                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6944                             checkCnt++;
6945                         if(checkCnt >= 2) {
6946                             reason = "Xboard adjudication: 3rd check";
6947                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6948                             break;
6949                         }
6950                     }
6951                 }
6952               case MT_NONE:
6953               default:
6954                 break;
6955               case MT_STALEMATE:
6956               case MT_STAINMATE:
6957                 reason = "Xboard adjudication: Stalemate";
6958                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6959                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6960                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6961                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6962                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6963                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6964                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6965                                                                         EP_CHECKMATE : EP_WINS);
6966                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6967                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6968                 }
6969                 break;
6970               case MT_CHECKMATE:
6971                 reason = "Xboard adjudication: Checkmate";
6972                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6973                 break;
6974             }
6975
6976                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6977                     case EP_STALEMATE:
6978                         result = GameIsDrawn; break;
6979                     case EP_CHECKMATE:
6980                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6981                     case EP_WINS:
6982                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6983                     default:
6984                         result = EndOfFile;
6985                 }
6986                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6987                     if(engineOpponent)
6988                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6989                     GameEnds( result, reason, GE_XBOARD );
6990                     return 1;
6991                 }
6992
6993                 /* Next absolutely insufficient mating material. */
6994                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6995                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6996                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6997
6998                      /* always flag draws, for judging claims */
6999                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7000
7001                      if(canAdjudicate && appData.materialDraws) {
7002                          /* but only adjudicate them if adjudication enabled */
7003                          if(engineOpponent) {
7004                            SendToProgram("force\n", engineOpponent); // suppress reply
7005                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7006                          }
7007                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7008                          return 1;
7009                      }
7010                 }
7011
7012                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7013                 if(gameInfo.variant == VariantXiangqi ?
7014                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7015                  : nrW + nrB == 4 &&
7016                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7017                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7018                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7019                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7020                    ) ) {
7021                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7022                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7023                           if(engineOpponent) {
7024                             SendToProgram("force\n", engineOpponent); // suppress reply
7025                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7026                           }
7027                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7028                           return 1;
7029                      }
7030                 } else moveCount = 6;
7031             }
7032         if (appData.debugMode) { int i;
7033             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7034                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7035                     appData.drawRepeats);
7036             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7037               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7038
7039         }
7040
7041         // Repetition draws and 50-move rule can be applied independently of legality testing
7042
7043                 /* Check for rep-draws */
7044                 count = 0;
7045                 for(k = forwardMostMove-2;
7046                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7047                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7048                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7049                     k-=2)
7050                 {   int rights=0;
7051                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7052                         /* compare castling rights */
7053                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7054                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7055                                 rights++; /* King lost rights, while rook still had them */
7056                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7057                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7058                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7059                                    rights++; /* but at least one rook lost them */
7060                         }
7061                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7062                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7063                                 rights++;
7064                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7065                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7066                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7067                                    rights++;
7068                         }
7069                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7070                             && appData.drawRepeats > 1) {
7071                              /* adjudicate after user-specified nr of repeats */
7072                              int result = GameIsDrawn;
7073                              char *details = "XBoard adjudication: repetition draw";
7074                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7075                                 // [HGM] xiangqi: check for forbidden perpetuals
7076                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7077                                 for(m=forwardMostMove; m>k; m-=2) {
7078                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7079                                         ourPerpetual = 0; // the current mover did not always check
7080                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7081                                         hisPerpetual = 0; // the opponent did not always check
7082                                 }
7083                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7084                                                                         ourPerpetual, hisPerpetual);
7085                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7086                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7087                                     details = "Xboard adjudication: perpetual checking";
7088                                 } else
7089                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7090                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7091                                 } else
7092                                 // Now check for perpetual chases
7093                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7094                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7095                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7096                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7097                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7098                                         details = "Xboard adjudication: perpetual chasing";
7099                                     } else
7100                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7101                                         break; // Abort repetition-checking loop.
7102                                 }
7103                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7104                              }
7105                              if(engineOpponent) {
7106                                SendToProgram("force\n", engineOpponent); // suppress reply
7107                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7108                              }
7109                              GameEnds( result, details, GE_XBOARD );
7110                              return 1;
7111                         }
7112                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7113                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7114                     }
7115                 }
7116
7117                 /* Now we test for 50-move draws. Determine ply count */
7118                 count = forwardMostMove;
7119                 /* look for last irreversble move */
7120                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7121                     count--;
7122                 /* if we hit starting position, add initial plies */
7123                 if( count == backwardMostMove )
7124                     count -= initialRulePlies;
7125                 count = forwardMostMove - count;
7126                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7127                         // adjust reversible move counter for checks in Xiangqi
7128                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7129                         if(i < backwardMostMove) i = backwardMostMove;
7130                         while(i <= forwardMostMove) {
7131                                 lastCheck = inCheck; // check evasion does not count
7132                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7133                                 if(inCheck || lastCheck) count--; // check does not count
7134                                 i++;
7135                         }
7136                 }
7137                 if( count >= 100)
7138                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7139                          /* this is used to judge if draw claims are legal */
7140                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7141                          if(engineOpponent) {
7142                            SendToProgram("force\n", engineOpponent); // suppress reply
7143                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7144                          }
7145                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7146                          return 1;
7147                 }
7148
7149                 /* if draw offer is pending, treat it as a draw claim
7150                  * when draw condition present, to allow engines a way to
7151                  * claim draws before making their move to avoid a race
7152                  * condition occurring after their move
7153                  */
7154                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7155                          char *p = NULL;
7156                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7157                              p = "Draw claim: 50-move rule";
7158                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7159                              p = "Draw claim: 3-fold repetition";
7160                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7161                              p = "Draw claim: insufficient mating material";
7162                          if( p != NULL && canAdjudicate) {
7163                              if(engineOpponent) {
7164                                SendToProgram("force\n", engineOpponent); // suppress reply
7165                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7166                              }
7167                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7168                              return 1;
7169                          }
7170                 }
7171
7172                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7173                     if(engineOpponent) {
7174                       SendToProgram("force\n", engineOpponent); // suppress reply
7175                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7176                     }
7177                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7178                     return 1;
7179                 }
7180         return 0;
7181 }
7182
7183 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7184 {   // [HGM] book: this routine intercepts moves to simulate book replies
7185     char *bookHit = NULL;
7186
7187     //first determine if the incoming move brings opponent into his book
7188     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7189         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7190     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7191     if(bookHit != NULL && !cps->bookSuspend) {
7192         // make sure opponent is not going to reply after receiving move to book position
7193         SendToProgram("force\n", cps);
7194         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7195     }
7196     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7197     // now arrange restart after book miss
7198     if(bookHit) {
7199         // after a book hit we never send 'go', and the code after the call to this routine
7200         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7201         char buf[MSG_SIZ];
7202         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7203         SendToProgram(buf, cps);
7204         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7205     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7206         SendToProgram("go\n", cps);
7207         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7208     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7209         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7210             SendToProgram("go\n", cps);
7211         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7212     }
7213     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7214 }
7215
7216 char *savedMessage;
7217 ChessProgramState *savedState;
7218 void DeferredBookMove(void)
7219 {
7220         if(savedState->lastPing != savedState->lastPong)
7221                     ScheduleDelayedEvent(DeferredBookMove, 10);
7222         else
7223         HandleMachineMove(savedMessage, savedState);
7224 }
7225
7226 void
7227 HandleMachineMove(message, cps)
7228      char *message;
7229      ChessProgramState *cps;
7230 {
7231     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7232     char realname[MSG_SIZ];
7233     int fromX, fromY, toX, toY;
7234     ChessMove moveType;
7235     char promoChar;
7236     char *p;
7237     int machineWhite;
7238     char *bookHit;
7239
7240     cps->userError = 0;
7241
7242 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7243     /*
7244      * Kludge to ignore BEL characters
7245      */
7246     while (*message == '\007') message++;
7247
7248     /*
7249      * [HGM] engine debug message: ignore lines starting with '#' character
7250      */
7251     if(cps->debug && *message == '#') return;
7252
7253     /*
7254      * Look for book output
7255      */
7256     if (cps == &first && bookRequested) {
7257         if (message[0] == '\t' || message[0] == ' ') {
7258             /* Part of the book output is here; append it */
7259             strcat(bookOutput, message);
7260             strcat(bookOutput, "  \n");
7261             return;
7262         } else if (bookOutput[0] != NULLCHAR) {
7263             /* All of book output has arrived; display it */
7264             char *p = bookOutput;
7265             while (*p != NULLCHAR) {
7266                 if (*p == '\t') *p = ' ';
7267                 p++;
7268             }
7269             DisplayInformation(bookOutput);
7270             bookRequested = FALSE;
7271             /* Fall through to parse the current output */
7272         }
7273     }
7274
7275     /*
7276      * Look for machine move.
7277      */
7278     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7279         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7280     {
7281         /* This method is only useful on engines that support ping */
7282         if (cps->lastPing != cps->lastPong) {
7283           if (gameMode == BeginningOfGame) {
7284             /* Extra move from before last new; ignore */
7285             if (appData.debugMode) {
7286                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7287             }
7288           } else {
7289             if (appData.debugMode) {
7290                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7291                         cps->which, gameMode);
7292             }
7293
7294             SendToProgram("undo\n", cps);
7295           }
7296           return;
7297         }
7298
7299         switch (gameMode) {
7300           case BeginningOfGame:
7301             /* Extra move from before last reset; ignore */
7302             if (appData.debugMode) {
7303                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7304             }
7305             return;
7306
7307           case EndOfGame:
7308           case IcsIdle:
7309           default:
7310             /* Extra move after we tried to stop.  The mode test is
7311                not a reliable way of detecting this problem, but it's
7312                the best we can do on engines that don't support ping.
7313             */
7314             if (appData.debugMode) {
7315                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7316                         cps->which, gameMode);
7317             }
7318             SendToProgram("undo\n", cps);
7319             return;
7320
7321           case MachinePlaysWhite:
7322           case IcsPlayingWhite:
7323             machineWhite = TRUE;
7324             break;
7325
7326           case MachinePlaysBlack:
7327           case IcsPlayingBlack:
7328             machineWhite = FALSE;
7329             break;
7330
7331           case TwoMachinesPlay:
7332             machineWhite = (cps->twoMachinesColor[0] == 'w');
7333             break;
7334         }
7335         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7336             if (appData.debugMode) {
7337                 fprintf(debugFP,
7338                         "Ignoring move out of turn by %s, gameMode %d"
7339                         ", forwardMost %d\n",
7340                         cps->which, gameMode, forwardMostMove);
7341             }
7342             return;
7343         }
7344
7345     if (appData.debugMode) { int f = forwardMostMove;
7346         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7347                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7348                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7349     }
7350         if(cps->alphaRank) AlphaRank(machineMove, 4);
7351         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7352                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7353             /* Machine move could not be parsed; ignore it. */
7354           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7355                     machineMove, _(cps->which));
7356             DisplayError(buf1, 0);
7357             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7358                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7359             if (gameMode == TwoMachinesPlay) {
7360               GameEnds(machineWhite ? BlackWins : WhiteWins,
7361                        buf1, GE_XBOARD);
7362             }
7363             return;
7364         }
7365
7366         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7367         /* So we have to redo legality test with true e.p. status here,  */
7368         /* to make sure an illegal e.p. capture does not slip through,   */
7369         /* to cause a forfeit on a justified illegal-move complaint      */
7370         /* of the opponent.                                              */
7371         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7372            ChessMove moveType;
7373            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7374                              fromY, fromX, toY, toX, promoChar);
7375             if (appData.debugMode) {
7376                 int i;
7377                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7378                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7379                 fprintf(debugFP, "castling rights\n");
7380             }
7381             if(moveType == IllegalMove) {
7382               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7383                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7384                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7385                            buf1, GE_XBOARD);
7386                 return;
7387            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7388            /* [HGM] Kludge to handle engines that send FRC-style castling
7389               when they shouldn't (like TSCP-Gothic) */
7390            switch(moveType) {
7391              case WhiteASideCastleFR:
7392              case BlackASideCastleFR:
7393                toX+=2;
7394                currentMoveString[2]++;
7395                break;
7396              case WhiteHSideCastleFR:
7397              case BlackHSideCastleFR:
7398                toX--;
7399                currentMoveString[2]--;
7400                break;
7401              default: ; // nothing to do, but suppresses warning of pedantic compilers
7402            }
7403         }
7404         hintRequested = FALSE;
7405         lastHint[0] = NULLCHAR;
7406         bookRequested = FALSE;
7407         /* Program may be pondering now */
7408         cps->maybeThinking = TRUE;
7409         if (cps->sendTime == 2) cps->sendTime = 1;
7410         if (cps->offeredDraw) cps->offeredDraw--;
7411
7412         /* [AS] Save move info*/
7413         pvInfoList[ forwardMostMove ].score = programStats.score;
7414         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7415         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7416
7417         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7418
7419         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7420         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7421             int count = 0;
7422
7423             while( count < adjudicateLossPlies ) {
7424                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7425
7426                 if( count & 1 ) {
7427                     score = -score; /* Flip score for winning side */
7428                 }
7429
7430                 if( score > adjudicateLossThreshold ) {
7431                     break;
7432                 }
7433
7434                 count++;
7435             }
7436
7437             if( count >= adjudicateLossPlies ) {
7438                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7439
7440                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7441                     "Xboard adjudication",
7442                     GE_XBOARD );
7443
7444                 return;
7445             }
7446         }
7447
7448         if(Adjudicate(cps)) {
7449             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7450             return; // [HGM] adjudicate: for all automatic game ends
7451         }
7452
7453 #if ZIPPY
7454         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7455             first.initDone) {
7456           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7457                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7458                 SendToICS("draw ");
7459                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7460           }
7461           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7462           ics_user_moved = 1;
7463           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7464                 char buf[3*MSG_SIZ];
7465
7466                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7467                         programStats.score / 100.,
7468                         programStats.depth,
7469                         programStats.time / 100.,
7470                         (unsigned int)programStats.nodes,
7471                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7472                         programStats.movelist);
7473                 SendToICS(buf);
7474 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7475           }
7476         }
7477 #endif
7478
7479         /* [AS] Clear stats for next move */
7480         ClearProgramStats();
7481         thinkOutput[0] = NULLCHAR;
7482         hiddenThinkOutputState = 0;
7483
7484         bookHit = NULL;
7485         if (gameMode == TwoMachinesPlay) {
7486             /* [HGM] relaying draw offers moved to after reception of move */
7487             /* and interpreting offer as claim if it brings draw condition */
7488             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7489                 SendToProgram("draw\n", cps->other);
7490             }
7491             if (cps->other->sendTime) {
7492                 SendTimeRemaining(cps->other,
7493                                   cps->other->twoMachinesColor[0] == 'w');
7494             }
7495             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7496             if (firstMove && !bookHit) {
7497                 firstMove = FALSE;
7498                 if (cps->other->useColors) {
7499                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7500                 }
7501                 SendToProgram("go\n", cps->other);
7502             }
7503             cps->other->maybeThinking = TRUE;
7504         }
7505
7506         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7507
7508         if (!pausing && appData.ringBellAfterMoves) {
7509             RingBell();
7510         }
7511
7512         /*
7513          * Reenable menu items that were disabled while
7514          * machine was thinking
7515          */
7516         if (gameMode != TwoMachinesPlay)
7517             SetUserThinkingEnables();
7518
7519         // [HGM] book: after book hit opponent has received move and is now in force mode
7520         // force the book reply into it, and then fake that it outputted this move by jumping
7521         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7522         if(bookHit) {
7523                 static char bookMove[MSG_SIZ]; // a bit generous?
7524
7525                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7526                 strcat(bookMove, bookHit);
7527                 message = bookMove;
7528                 cps = cps->other;
7529                 programStats.nodes = programStats.depth = programStats.time =
7530                 programStats.score = programStats.got_only_move = 0;
7531                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7532
7533                 if(cps->lastPing != cps->lastPong) {
7534                     savedMessage = message; // args for deferred call
7535                     savedState = cps;
7536                     ScheduleDelayedEvent(DeferredBookMove, 10);
7537                     return;
7538                 }
7539                 goto FakeBookMove;
7540         }
7541
7542         return;
7543     }
7544
7545     /* Set special modes for chess engines.  Later something general
7546      *  could be added here; for now there is just one kludge feature,
7547      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7548      *  when "xboard" is given as an interactive command.
7549      */
7550     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7551         cps->useSigint = FALSE;
7552         cps->useSigterm = FALSE;
7553     }
7554     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7555       ParseFeatures(message+8, cps);
7556       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7557     }
7558
7559     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7560       int dummy, s=6; char buf[MSG_SIZ];
7561       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7562       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7563       ParseFEN(boards[0], &dummy, message+s);
7564       DrawPosition(TRUE, boards[0]);
7565       startedFromSetupPosition = TRUE;
7566       return;
7567     }
7568     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7569      * want this, I was asked to put it in, and obliged.
7570      */
7571     if (!strncmp(message, "setboard ", 9)) {
7572         Board initial_position;
7573
7574         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7575
7576         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7577             DisplayError(_("Bad FEN received from engine"), 0);
7578             return ;
7579         } else {
7580            Reset(TRUE, FALSE);
7581            CopyBoard(boards[0], initial_position);
7582            initialRulePlies = FENrulePlies;
7583            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7584            else gameMode = MachinePlaysBlack;
7585            DrawPosition(FALSE, boards[currentMove]);
7586         }
7587         return;
7588     }
7589
7590     /*
7591      * Look for communication commands
7592      */
7593     if (!strncmp(message, "telluser ", 9)) {
7594         if(message[9] == '\\' && message[10] == '\\')
7595             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7596         DisplayNote(message + 9);
7597         return;
7598     }
7599     if (!strncmp(message, "tellusererror ", 14)) {
7600         cps->userError = 1;
7601         if(message[14] == '\\' && message[15] == '\\')
7602             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7603         DisplayError(message + 14, 0);
7604         return;
7605     }
7606     if (!strncmp(message, "tellopponent ", 13)) {
7607       if (appData.icsActive) {
7608         if (loggedOn) {
7609           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7610           SendToICS(buf1);
7611         }
7612       } else {
7613         DisplayNote(message + 13);
7614       }
7615       return;
7616     }
7617     if (!strncmp(message, "tellothers ", 11)) {
7618       if (appData.icsActive) {
7619         if (loggedOn) {
7620           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7621           SendToICS(buf1);
7622         }
7623       }
7624       return;
7625     }
7626     if (!strncmp(message, "tellall ", 8)) {
7627       if (appData.icsActive) {
7628         if (loggedOn) {
7629           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7630           SendToICS(buf1);
7631         }
7632       } else {
7633         DisplayNote(message + 8);
7634       }
7635       return;
7636     }
7637     if (strncmp(message, "warning", 7) == 0) {
7638         /* Undocumented feature, use tellusererror in new code */
7639         DisplayError(message, 0);
7640         return;
7641     }
7642     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7643         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7644         strcat(realname, " query");
7645         AskQuestion(realname, buf2, buf1, cps->pr);
7646         return;
7647     }
7648     /* Commands from the engine directly to ICS.  We don't allow these to be
7649      *  sent until we are logged on. Crafty kibitzes have been known to
7650      *  interfere with the login process.
7651      */
7652     if (loggedOn) {
7653         if (!strncmp(message, "tellics ", 8)) {
7654             SendToICS(message + 8);
7655             SendToICS("\n");
7656             return;
7657         }
7658         if (!strncmp(message, "tellicsnoalias ", 15)) {
7659             SendToICS(ics_prefix);
7660             SendToICS(message + 15);
7661             SendToICS("\n");
7662             return;
7663         }
7664         /* The following are for backward compatibility only */
7665         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7666             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7667             SendToICS(ics_prefix);
7668             SendToICS(message);
7669             SendToICS("\n");
7670             return;
7671         }
7672     }
7673     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7674         return;
7675     }
7676     /*
7677      * If the move is illegal, cancel it and redraw the board.
7678      * Also deal with other error cases.  Matching is rather loose
7679      * here to accommodate engines written before the spec.
7680      */
7681     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7682         strncmp(message, "Error", 5) == 0) {
7683         if (StrStr(message, "name") ||
7684             StrStr(message, "rating") || StrStr(message, "?") ||
7685             StrStr(message, "result") || StrStr(message, "board") ||
7686             StrStr(message, "bk") || StrStr(message, "computer") ||
7687             StrStr(message, "variant") || StrStr(message, "hint") ||
7688             StrStr(message, "random") || StrStr(message, "depth") ||
7689             StrStr(message, "accepted")) {
7690             return;
7691         }
7692         if (StrStr(message, "protover")) {
7693           /* Program is responding to input, so it's apparently done
7694              initializing, and this error message indicates it is
7695              protocol version 1.  So we don't need to wait any longer
7696              for it to initialize and send feature commands. */
7697           FeatureDone(cps, 1);
7698           cps->protocolVersion = 1;
7699           return;
7700         }
7701         cps->maybeThinking = FALSE;
7702
7703         if (StrStr(message, "draw")) {
7704             /* Program doesn't have "draw" command */
7705             cps->sendDrawOffers = 0;
7706             return;
7707         }
7708         if (cps->sendTime != 1 &&
7709             (StrStr(message, "time") || StrStr(message, "otim"))) {
7710           /* Program apparently doesn't have "time" or "otim" command */
7711           cps->sendTime = 0;
7712           return;
7713         }
7714         if (StrStr(message, "analyze")) {
7715             cps->analysisSupport = FALSE;
7716             cps->analyzing = FALSE;
7717             Reset(FALSE, TRUE);
7718             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7719             DisplayError(buf2, 0);
7720             return;
7721         }
7722         if (StrStr(message, "(no matching move)st")) {
7723           /* Special kludge for GNU Chess 4 only */
7724           cps->stKludge = TRUE;
7725           SendTimeControl(cps, movesPerSession, timeControl,
7726                           timeIncrement, appData.searchDepth,
7727                           searchTime);
7728           return;
7729         }
7730         if (StrStr(message, "(no matching move)sd")) {
7731           /* Special kludge for GNU Chess 4 only */
7732           cps->sdKludge = TRUE;
7733           SendTimeControl(cps, movesPerSession, timeControl,
7734                           timeIncrement, appData.searchDepth,
7735                           searchTime);
7736           return;
7737         }
7738         if (!StrStr(message, "llegal")) {
7739             return;
7740         }
7741         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7742             gameMode == IcsIdle) return;
7743         if (forwardMostMove <= backwardMostMove) return;
7744         if (pausing) PauseEvent();
7745       if(appData.forceIllegal) {
7746             // [HGM] illegal: machine refused move; force position after move into it
7747           SendToProgram("force\n", cps);
7748           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7749                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7750                 // when black is to move, while there might be nothing on a2 or black
7751                 // might already have the move. So send the board as if white has the move.
7752                 // But first we must change the stm of the engine, as it refused the last move
7753                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7754                 if(WhiteOnMove(forwardMostMove)) {
7755                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7756                     SendBoard(cps, forwardMostMove); // kludgeless board
7757                 } else {
7758                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7759                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7760                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7761                 }
7762           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7763             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7764                  gameMode == TwoMachinesPlay)
7765               SendToProgram("go\n", cps);
7766             return;
7767       } else
7768         if (gameMode == PlayFromGameFile) {
7769             /* Stop reading this game file */
7770             gameMode = EditGame;
7771             ModeHighlight();
7772         }
7773         /* [HGM] illegal-move claim should forfeit game when Xboard */
7774         /* only passes fully legal moves                            */
7775         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7776             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7777                                 "False illegal-move claim", GE_XBOARD );
7778             return; // do not take back move we tested as valid
7779         }
7780         currentMove = forwardMostMove-1;
7781         DisplayMove(currentMove-1); /* before DisplayMoveError */
7782         SwitchClocks(forwardMostMove-1); // [HGM] race
7783         DisplayBothClocks();
7784         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7785                 parseList[currentMove], _(cps->which));
7786         DisplayMoveError(buf1);
7787         DrawPosition(FALSE, boards[currentMove]);
7788         return;
7789     }
7790     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7791         /* Program has a broken "time" command that
7792            outputs a string not ending in newline.
7793            Don't use it. */
7794         cps->sendTime = 0;
7795     }
7796
7797     /*
7798      * If chess program startup fails, exit with an error message.
7799      * Attempts to recover here are futile.
7800      */
7801     if ((StrStr(message, "unknown host") != NULL)
7802         || (StrStr(message, "No remote directory") != NULL)
7803         || (StrStr(message, "not found") != NULL)
7804         || (StrStr(message, "No such file") != NULL)
7805         || (StrStr(message, "can't alloc") != NULL)
7806         || (StrStr(message, "Permission denied") != NULL)) {
7807
7808         cps->maybeThinking = FALSE;
7809         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7810                 _(cps->which), cps->program, cps->host, message);
7811         RemoveInputSource(cps->isr);
7812         DisplayFatalError(buf1, 0, 1);
7813         return;
7814     }
7815
7816     /*
7817      * Look for hint output
7818      */
7819     if (sscanf(message, "Hint: %s", buf1) == 1) {
7820         if (cps == &first && hintRequested) {
7821             hintRequested = FALSE;
7822             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7823                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7824                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7825                                     PosFlags(forwardMostMove),
7826                                     fromY, fromX, toY, toX, promoChar, buf1);
7827                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7828                 DisplayInformation(buf2);
7829             } else {
7830                 /* Hint move could not be parsed!? */
7831               snprintf(buf2, sizeof(buf2),
7832                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7833                         buf1, _(cps->which));
7834                 DisplayError(buf2, 0);
7835             }
7836         } else {
7837           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7838         }
7839         return;
7840     }
7841
7842     /*
7843      * Ignore other messages if game is not in progress
7844      */
7845     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7846         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7847
7848     /*
7849      * look for win, lose, draw, or draw offer
7850      */
7851     if (strncmp(message, "1-0", 3) == 0) {
7852         char *p, *q, *r = "";
7853         p = strchr(message, '{');
7854         if (p) {
7855             q = strchr(p, '}');
7856             if (q) {
7857                 *q = NULLCHAR;
7858                 r = p + 1;
7859             }
7860         }
7861         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7862         return;
7863     } else if (strncmp(message, "0-1", 3) == 0) {
7864         char *p, *q, *r = "";
7865         p = strchr(message, '{');
7866         if (p) {
7867             q = strchr(p, '}');
7868             if (q) {
7869                 *q = NULLCHAR;
7870                 r = p + 1;
7871             }
7872         }
7873         /* Kludge for Arasan 4.1 bug */
7874         if (strcmp(r, "Black resigns") == 0) {
7875             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7876             return;
7877         }
7878         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7879         return;
7880     } else if (strncmp(message, "1/2", 3) == 0) {
7881         char *p, *q, *r = "";
7882         p = strchr(message, '{');
7883         if (p) {
7884             q = strchr(p, '}');
7885             if (q) {
7886                 *q = NULLCHAR;
7887                 r = p + 1;
7888             }
7889         }
7890
7891         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7892         return;
7893
7894     } else if (strncmp(message, "White resign", 12) == 0) {
7895         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7896         return;
7897     } else if (strncmp(message, "Black resign", 12) == 0) {
7898         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7899         return;
7900     } else if (strncmp(message, "White matches", 13) == 0 ||
7901                strncmp(message, "Black matches", 13) == 0   ) {
7902         /* [HGM] ignore GNUShogi noises */
7903         return;
7904     } else if (strncmp(message, "White", 5) == 0 &&
7905                message[5] != '(' &&
7906                StrStr(message, "Black") == NULL) {
7907         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7908         return;
7909     } else if (strncmp(message, "Black", 5) == 0 &&
7910                message[5] != '(') {
7911         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7912         return;
7913     } else if (strcmp(message, "resign") == 0 ||
7914                strcmp(message, "computer resigns") == 0) {
7915         switch (gameMode) {
7916           case MachinePlaysBlack:
7917           case IcsPlayingBlack:
7918             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7919             break;
7920           case MachinePlaysWhite:
7921           case IcsPlayingWhite:
7922             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7923             break;
7924           case TwoMachinesPlay:
7925             if (cps->twoMachinesColor[0] == 'w')
7926               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7927             else
7928               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7929             break;
7930           default:
7931             /* can't happen */
7932             break;
7933         }
7934         return;
7935     } else if (strncmp(message, "opponent mates", 14) == 0) {
7936         switch (gameMode) {
7937           case MachinePlaysBlack:
7938           case IcsPlayingBlack:
7939             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7940             break;
7941           case MachinePlaysWhite:
7942           case IcsPlayingWhite:
7943             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7944             break;
7945           case TwoMachinesPlay:
7946             if (cps->twoMachinesColor[0] == 'w')
7947               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7948             else
7949               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7950             break;
7951           default:
7952             /* can't happen */
7953             break;
7954         }
7955         return;
7956     } else if (strncmp(message, "computer mates", 14) == 0) {
7957         switch (gameMode) {
7958           case MachinePlaysBlack:
7959           case IcsPlayingBlack:
7960             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7961             break;
7962           case MachinePlaysWhite:
7963           case IcsPlayingWhite:
7964             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7965             break;
7966           case TwoMachinesPlay:
7967             if (cps->twoMachinesColor[0] == 'w')
7968               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7969             else
7970               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7971             break;
7972           default:
7973             /* can't happen */
7974             break;
7975         }
7976         return;
7977     } else if (strncmp(message, "checkmate", 9) == 0) {
7978         if (WhiteOnMove(forwardMostMove)) {
7979             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7980         } else {
7981             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7982         }
7983         return;
7984     } else if (strstr(message, "Draw") != NULL ||
7985                strstr(message, "game is a draw") != NULL) {
7986         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7987         return;
7988     } else if (strstr(message, "offer") != NULL &&
7989                strstr(message, "draw") != NULL) {
7990 #if ZIPPY
7991         if (appData.zippyPlay && first.initDone) {
7992             /* Relay offer to ICS */
7993             SendToICS(ics_prefix);
7994             SendToICS("draw\n");
7995         }
7996 #endif
7997         cps->offeredDraw = 2; /* valid until this engine moves twice */
7998         if (gameMode == TwoMachinesPlay) {
7999             if (cps->other->offeredDraw) {
8000                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8001             /* [HGM] in two-machine mode we delay relaying draw offer      */
8002             /* until after we also have move, to see if it is really claim */
8003             }
8004         } else if (gameMode == MachinePlaysWhite ||
8005                    gameMode == MachinePlaysBlack) {
8006           if (userOfferedDraw) {
8007             DisplayInformation(_("Machine accepts your draw offer"));
8008             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8009           } else {
8010             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8011           }
8012         }
8013     }
8014
8015
8016     /*
8017      * Look for thinking output
8018      */
8019     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8020           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8021                                 ) {
8022         int plylev, mvleft, mvtot, curscore, time;
8023         char mvname[MOVE_LEN];
8024         u64 nodes; // [DM]
8025         char plyext;
8026         int ignore = FALSE;
8027         int prefixHint = FALSE;
8028         mvname[0] = NULLCHAR;
8029
8030         switch (gameMode) {
8031           case MachinePlaysBlack:
8032           case IcsPlayingBlack:
8033             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8034             break;
8035           case MachinePlaysWhite:
8036           case IcsPlayingWhite:
8037             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8038             break;
8039           case AnalyzeMode:
8040           case AnalyzeFile:
8041             break;
8042           case IcsObserving: /* [DM] icsEngineAnalyze */
8043             if (!appData.icsEngineAnalyze) ignore = TRUE;
8044             break;
8045           case TwoMachinesPlay:
8046             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8047                 ignore = TRUE;
8048             }
8049             break;
8050           default:
8051             ignore = TRUE;
8052             break;
8053         }
8054
8055         if (!ignore) {
8056             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8057             buf1[0] = NULLCHAR;
8058             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8059                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8060
8061                 if (plyext != ' ' && plyext != '\t') {
8062                     time *= 100;
8063                 }
8064
8065                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8066                 if( cps->scoreIsAbsolute &&
8067                     ( gameMode == MachinePlaysBlack ||
8068                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8069                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8070                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8071                      !WhiteOnMove(currentMove)
8072                     ) )
8073                 {
8074                     curscore = -curscore;
8075                 }
8076
8077
8078                 tempStats.depth = plylev;
8079                 tempStats.nodes = nodes;
8080                 tempStats.time = time;
8081                 tempStats.score = curscore;
8082                 tempStats.got_only_move = 0;
8083
8084                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8085                         int ticklen;
8086
8087                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8088                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8089                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8090                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8091                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8092                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8093                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8094                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8095                 }
8096
8097                 /* Buffer overflow protection */
8098                 if (buf1[0] != NULLCHAR) {
8099                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8100                         && appData.debugMode) {
8101                         fprintf(debugFP,
8102                                 "PV is too long; using the first %u bytes.\n",
8103                                 (unsigned) sizeof(tempStats.movelist) - 1);
8104                     }
8105
8106                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8107                 } else {
8108                     sprintf(tempStats.movelist, " no PV\n");
8109                 }
8110
8111                 if (tempStats.seen_stat) {
8112                     tempStats.ok_to_send = 1;
8113                 }
8114
8115                 if (strchr(tempStats.movelist, '(') != NULL) {
8116                     tempStats.line_is_book = 1;
8117                     tempStats.nr_moves = 0;
8118                     tempStats.moves_left = 0;
8119                 } else {
8120                     tempStats.line_is_book = 0;
8121                 }
8122
8123                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8124                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8125
8126                 SendProgramStatsToFrontend( cps, &tempStats );
8127
8128                 /*
8129                     [AS] Protect the thinkOutput buffer from overflow... this
8130                     is only useful if buf1 hasn't overflowed first!
8131                 */
8132                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8133                          plylev,
8134                          (gameMode == TwoMachinesPlay ?
8135                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8136                          ((double) curscore) / 100.0,
8137                          prefixHint ? lastHint : "",
8138                          prefixHint ? " " : "" );
8139
8140                 if( buf1[0] != NULLCHAR ) {
8141                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8142
8143                     if( strlen(buf1) > max_len ) {
8144                         if( appData.debugMode) {
8145                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8146                         }
8147                         buf1[max_len+1] = '\0';
8148                     }
8149
8150                     strcat( thinkOutput, buf1 );
8151                 }
8152
8153                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8154                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8155                     DisplayMove(currentMove - 1);
8156                 }
8157                 return;
8158
8159             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8160                 /* crafty (9.25+) says "(only move) <move>"
8161                  * if there is only 1 legal move
8162                  */
8163                 sscanf(p, "(only move) %s", buf1);
8164                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8165                 sprintf(programStats.movelist, "%s (only move)", buf1);
8166                 programStats.depth = 1;
8167                 programStats.nr_moves = 1;
8168                 programStats.moves_left = 1;
8169                 programStats.nodes = 1;
8170                 programStats.time = 1;
8171                 programStats.got_only_move = 1;
8172
8173                 /* Not really, but we also use this member to
8174                    mean "line isn't going to change" (Crafty
8175                    isn't searching, so stats won't change) */
8176                 programStats.line_is_book = 1;
8177
8178                 SendProgramStatsToFrontend( cps, &programStats );
8179
8180                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8181                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8182                     DisplayMove(currentMove - 1);
8183                 }
8184                 return;
8185             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8186                               &time, &nodes, &plylev, &mvleft,
8187                               &mvtot, mvname) >= 5) {
8188                 /* The stat01: line is from Crafty (9.29+) in response
8189                    to the "." command */
8190                 programStats.seen_stat = 1;
8191                 cps->maybeThinking = TRUE;
8192
8193                 if (programStats.got_only_move || !appData.periodicUpdates)
8194                   return;
8195
8196                 programStats.depth = plylev;
8197                 programStats.time = time;
8198                 programStats.nodes = nodes;
8199                 programStats.moves_left = mvleft;
8200                 programStats.nr_moves = mvtot;
8201                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8202                 programStats.ok_to_send = 1;
8203                 programStats.movelist[0] = '\0';
8204
8205                 SendProgramStatsToFrontend( cps, &programStats );
8206
8207                 return;
8208
8209             } else if (strncmp(message,"++",2) == 0) {
8210                 /* Crafty 9.29+ outputs this */
8211                 programStats.got_fail = 2;
8212                 return;
8213
8214             } else if (strncmp(message,"--",2) == 0) {
8215                 /* Crafty 9.29+ outputs this */
8216                 programStats.got_fail = 1;
8217                 return;
8218
8219             } else if (thinkOutput[0] != NULLCHAR &&
8220                        strncmp(message, "    ", 4) == 0) {
8221                 unsigned message_len;
8222
8223                 p = message;
8224                 while (*p && *p == ' ') p++;
8225
8226                 message_len = strlen( p );
8227
8228                 /* [AS] Avoid buffer overflow */
8229                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8230                     strcat(thinkOutput, " ");
8231                     strcat(thinkOutput, p);
8232                 }
8233
8234                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8235                     strcat(programStats.movelist, " ");
8236                     strcat(programStats.movelist, p);
8237                 }
8238
8239                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8240                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8241                     DisplayMove(currentMove - 1);
8242                 }
8243                 return;
8244             }
8245         }
8246         else {
8247             buf1[0] = NULLCHAR;
8248
8249             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8250                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8251             {
8252                 ChessProgramStats cpstats;
8253
8254                 if (plyext != ' ' && plyext != '\t') {
8255                     time *= 100;
8256                 }
8257
8258                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8259                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8260                     curscore = -curscore;
8261                 }
8262
8263                 cpstats.depth = plylev;
8264                 cpstats.nodes = nodes;
8265                 cpstats.time = time;
8266                 cpstats.score = curscore;
8267                 cpstats.got_only_move = 0;
8268                 cpstats.movelist[0] = '\0';
8269
8270                 if (buf1[0] != NULLCHAR) {
8271                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8272                 }
8273
8274                 cpstats.ok_to_send = 0;
8275                 cpstats.line_is_book = 0;
8276                 cpstats.nr_moves = 0;
8277                 cpstats.moves_left = 0;
8278
8279                 SendProgramStatsToFrontend( cps, &cpstats );
8280             }
8281         }
8282     }
8283 }
8284
8285
8286 /* Parse a game score from the character string "game", and
8287    record it as the history of the current game.  The game
8288    score is NOT assumed to start from the standard position.
8289    The display is not updated in any way.
8290    */
8291 void
8292 ParseGameHistory(game)
8293      char *game;
8294 {
8295     ChessMove moveType;
8296     int fromX, fromY, toX, toY, boardIndex;
8297     char promoChar;
8298     char *p, *q;
8299     char buf[MSG_SIZ];
8300
8301     if (appData.debugMode)
8302       fprintf(debugFP, "Parsing game history: %s\n", game);
8303
8304     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8305     gameInfo.site = StrSave(appData.icsHost);
8306     gameInfo.date = PGNDate();
8307     gameInfo.round = StrSave("-");
8308
8309     /* Parse out names of players */
8310     while (*game == ' ') game++;
8311     p = buf;
8312     while (*game != ' ') *p++ = *game++;
8313     *p = NULLCHAR;
8314     gameInfo.white = StrSave(buf);
8315     while (*game == ' ') game++;
8316     p = buf;
8317     while (*game != ' ' && *game != '\n') *p++ = *game++;
8318     *p = NULLCHAR;
8319     gameInfo.black = StrSave(buf);
8320
8321     /* Parse moves */
8322     boardIndex = blackPlaysFirst ? 1 : 0;
8323     yynewstr(game);
8324     for (;;) {
8325         yyboardindex = boardIndex;
8326         moveType = (ChessMove) Myylex();
8327         switch (moveType) {
8328           case IllegalMove:             /* maybe suicide chess, etc. */
8329   if (appData.debugMode) {
8330     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8331     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8332     setbuf(debugFP, NULL);
8333   }
8334           case WhitePromotion:
8335           case BlackPromotion:
8336           case WhiteNonPromotion:
8337           case BlackNonPromotion:
8338           case NormalMove:
8339           case WhiteCapturesEnPassant:
8340           case BlackCapturesEnPassant:
8341           case WhiteKingSideCastle:
8342           case WhiteQueenSideCastle:
8343           case BlackKingSideCastle:
8344           case BlackQueenSideCastle:
8345           case WhiteKingSideCastleWild:
8346           case WhiteQueenSideCastleWild:
8347           case BlackKingSideCastleWild:
8348           case BlackQueenSideCastleWild:
8349           /* PUSH Fabien */
8350           case WhiteHSideCastleFR:
8351           case WhiteASideCastleFR:
8352           case BlackHSideCastleFR:
8353           case BlackASideCastleFR:
8354           /* POP Fabien */
8355             fromX = currentMoveString[0] - AAA;
8356             fromY = currentMoveString[1] - ONE;
8357             toX = currentMoveString[2] - AAA;
8358             toY = currentMoveString[3] - ONE;
8359             promoChar = currentMoveString[4];
8360             break;
8361           case WhiteDrop:
8362           case BlackDrop:
8363             fromX = moveType == WhiteDrop ?
8364               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8365             (int) CharToPiece(ToLower(currentMoveString[0]));
8366             fromY = DROP_RANK;
8367             toX = currentMoveString[2] - AAA;
8368             toY = currentMoveString[3] - ONE;
8369             promoChar = NULLCHAR;
8370             break;
8371           case AmbiguousMove:
8372             /* bug? */
8373             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8374   if (appData.debugMode) {
8375     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8376     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8377     setbuf(debugFP, NULL);
8378   }
8379             DisplayError(buf, 0);
8380             return;
8381           case ImpossibleMove:
8382             /* bug? */
8383             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8384   if (appData.debugMode) {
8385     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8386     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8387     setbuf(debugFP, NULL);
8388   }
8389             DisplayError(buf, 0);
8390             return;
8391           case EndOfFile:
8392             if (boardIndex < backwardMostMove) {
8393                 /* Oops, gap.  How did that happen? */
8394                 DisplayError(_("Gap in move list"), 0);
8395                 return;
8396             }
8397             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8398             if (boardIndex > forwardMostMove) {
8399                 forwardMostMove = boardIndex;
8400             }
8401             return;
8402           case ElapsedTime:
8403             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8404                 strcat(parseList[boardIndex-1], " ");
8405                 strcat(parseList[boardIndex-1], yy_text);
8406             }
8407             continue;
8408           case Comment:
8409           case PGNTag:
8410           case NAG:
8411           default:
8412             /* ignore */
8413             continue;
8414           case WhiteWins:
8415           case BlackWins:
8416           case GameIsDrawn:
8417           case GameUnfinished:
8418             if (gameMode == IcsExamining) {
8419                 if (boardIndex < backwardMostMove) {
8420                     /* Oops, gap.  How did that happen? */
8421                     return;
8422                 }
8423                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8424                 return;
8425             }
8426             gameInfo.result = moveType;
8427             p = strchr(yy_text, '{');
8428             if (p == NULL) p = strchr(yy_text, '(');
8429             if (p == NULL) {
8430                 p = yy_text;
8431                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8432             } else {
8433                 q = strchr(p, *p == '{' ? '}' : ')');
8434                 if (q != NULL) *q = NULLCHAR;
8435                 p++;
8436             }
8437             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8438             gameInfo.resultDetails = StrSave(p);
8439             continue;
8440         }
8441         if (boardIndex >= forwardMostMove &&
8442             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8443             backwardMostMove = blackPlaysFirst ? 1 : 0;
8444             return;
8445         }
8446         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8447                                  fromY, fromX, toY, toX, promoChar,
8448                                  parseList[boardIndex]);
8449         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8450         /* currentMoveString is set as a side-effect of yylex */
8451         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8452         strcat(moveList[boardIndex], "\n");
8453         boardIndex++;
8454         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8455         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8456           case MT_NONE:
8457           case MT_STALEMATE:
8458           default:
8459             break;
8460           case MT_CHECK:
8461             if(gameInfo.variant != VariantShogi)
8462                 strcat(parseList[boardIndex - 1], "+");
8463             break;
8464           case MT_CHECKMATE:
8465           case MT_STAINMATE:
8466             strcat(parseList[boardIndex - 1], "#");
8467             break;
8468         }
8469     }
8470 }
8471
8472
8473 /* Apply a move to the given board  */
8474 void
8475 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8476      int fromX, fromY, toX, toY;
8477      int promoChar;
8478      Board board;
8479 {
8480   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8481   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8482
8483     /* [HGM] compute & store e.p. status and castling rights for new position */
8484     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8485
8486       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8487       oldEP = (signed char)board[EP_STATUS];
8488       board[EP_STATUS] = EP_NONE;
8489
8490       if( board[toY][toX] != EmptySquare )
8491            board[EP_STATUS] = EP_CAPTURE;
8492
8493   if (fromY == DROP_RANK) {
8494         /* must be first */
8495         piece = board[toY][toX] = (ChessSquare) fromX;
8496   } else {
8497       int i;
8498
8499       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8500            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8501                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8502       } else
8503       if( board[fromY][fromX] == WhitePawn ) {
8504            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8505                board[EP_STATUS] = EP_PAWN_MOVE;
8506            if( toY-fromY==2) {
8507                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8508                         gameInfo.variant != VariantBerolina || toX < fromX)
8509                       board[EP_STATUS] = toX | berolina;
8510                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8511                         gameInfo.variant != VariantBerolina || toX > fromX)
8512                       board[EP_STATUS] = toX;
8513            }
8514       } else
8515       if( board[fromY][fromX] == BlackPawn ) {
8516            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8517                board[EP_STATUS] = EP_PAWN_MOVE;
8518            if( toY-fromY== -2) {
8519                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8520                         gameInfo.variant != VariantBerolina || toX < fromX)
8521                       board[EP_STATUS] = toX | berolina;
8522                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8523                         gameInfo.variant != VariantBerolina || toX > fromX)
8524                       board[EP_STATUS] = toX;
8525            }
8526        }
8527
8528        for(i=0; i<nrCastlingRights; i++) {
8529            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8530               board[CASTLING][i] == toX   && castlingRank[i] == toY
8531              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8532        }
8533
8534      if (fromX == toX && fromY == toY) return;
8535
8536      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8537      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8538      if(gameInfo.variant == VariantKnightmate)
8539          king += (int) WhiteUnicorn - (int) WhiteKing;
8540
8541     /* Code added by Tord: */
8542     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8543     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8544         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8545       board[fromY][fromX] = EmptySquare;
8546       board[toY][toX] = EmptySquare;
8547       if((toX > fromX) != (piece == WhiteRook)) {
8548         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8549       } else {
8550         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8551       }
8552     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8553                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8554       board[fromY][fromX] = EmptySquare;
8555       board[toY][toX] = EmptySquare;
8556       if((toX > fromX) != (piece == BlackRook)) {
8557         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8558       } else {
8559         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8560       }
8561     /* End of code added by Tord */
8562
8563     } else if (board[fromY][fromX] == king
8564         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8565         && toY == fromY && toX > fromX+1) {
8566         board[fromY][fromX] = EmptySquare;
8567         board[toY][toX] = king;
8568         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8569         board[fromY][BOARD_RGHT-1] = EmptySquare;
8570     } else if (board[fromY][fromX] == king
8571         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8572                && toY == fromY && toX < fromX-1) {
8573         board[fromY][fromX] = EmptySquare;
8574         board[toY][toX] = king;
8575         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8576         board[fromY][BOARD_LEFT] = EmptySquare;
8577     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8578                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8579                && toY >= BOARD_HEIGHT-promoRank
8580                ) {
8581         /* white pawn promotion */
8582         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8583         if (board[toY][toX] == EmptySquare) {
8584             board[toY][toX] = WhiteQueen;
8585         }
8586         if(gameInfo.variant==VariantBughouse ||
8587            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8588             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8589         board[fromY][fromX] = EmptySquare;
8590     } else if ((fromY == BOARD_HEIGHT-4)
8591                && (toX != fromX)
8592                && gameInfo.variant != VariantXiangqi
8593                && gameInfo.variant != VariantBerolina
8594                && (board[fromY][fromX] == WhitePawn)
8595                && (board[toY][toX] == EmptySquare)) {
8596         board[fromY][fromX] = EmptySquare;
8597         board[toY][toX] = WhitePawn;
8598         captured = board[toY - 1][toX];
8599         board[toY - 1][toX] = EmptySquare;
8600     } else if ((fromY == BOARD_HEIGHT-4)
8601                && (toX == fromX)
8602                && gameInfo.variant == VariantBerolina
8603                && (board[fromY][fromX] == WhitePawn)
8604                && (board[toY][toX] == EmptySquare)) {
8605         board[fromY][fromX] = EmptySquare;
8606         board[toY][toX] = WhitePawn;
8607         if(oldEP & EP_BEROLIN_A) {
8608                 captured = board[fromY][fromX-1];
8609                 board[fromY][fromX-1] = EmptySquare;
8610         }else{  captured = board[fromY][fromX+1];
8611                 board[fromY][fromX+1] = EmptySquare;
8612         }
8613     } else if (board[fromY][fromX] == king
8614         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8615                && toY == fromY && toX > fromX+1) {
8616         board[fromY][fromX] = EmptySquare;
8617         board[toY][toX] = king;
8618         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8619         board[fromY][BOARD_RGHT-1] = EmptySquare;
8620     } else if (board[fromY][fromX] == king
8621         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8622                && toY == fromY && toX < fromX-1) {
8623         board[fromY][fromX] = EmptySquare;
8624         board[toY][toX] = king;
8625         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8626         board[fromY][BOARD_LEFT] = EmptySquare;
8627     } else if (fromY == 7 && fromX == 3
8628                && board[fromY][fromX] == BlackKing
8629                && toY == 7 && toX == 5) {
8630         board[fromY][fromX] = EmptySquare;
8631         board[toY][toX] = BlackKing;
8632         board[fromY][7] = EmptySquare;
8633         board[toY][4] = BlackRook;
8634     } else if (fromY == 7 && fromX == 3
8635                && board[fromY][fromX] == BlackKing
8636                && toY == 7 && toX == 1) {
8637         board[fromY][fromX] = EmptySquare;
8638         board[toY][toX] = BlackKing;
8639         board[fromY][0] = EmptySquare;
8640         board[toY][2] = BlackRook;
8641     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8642                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8643                && toY < promoRank
8644                ) {
8645         /* black pawn promotion */
8646         board[toY][toX] = CharToPiece(ToLower(promoChar));
8647         if (board[toY][toX] == EmptySquare) {
8648             board[toY][toX] = BlackQueen;
8649         }
8650         if(gameInfo.variant==VariantBughouse ||
8651            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8652             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8653         board[fromY][fromX] = EmptySquare;
8654     } else if ((fromY == 3)
8655                && (toX != fromX)
8656                && gameInfo.variant != VariantXiangqi
8657                && gameInfo.variant != VariantBerolina
8658                && (board[fromY][fromX] == BlackPawn)
8659                && (board[toY][toX] == EmptySquare)) {
8660         board[fromY][fromX] = EmptySquare;
8661         board[toY][toX] = BlackPawn;
8662         captured = board[toY + 1][toX];
8663         board[toY + 1][toX] = EmptySquare;
8664     } else if ((fromY == 3)
8665                && (toX == fromX)
8666                && gameInfo.variant == VariantBerolina
8667                && (board[fromY][fromX] == BlackPawn)
8668                && (board[toY][toX] == EmptySquare)) {
8669         board[fromY][fromX] = EmptySquare;
8670         board[toY][toX] = BlackPawn;
8671         if(oldEP & EP_BEROLIN_A) {
8672                 captured = board[fromY][fromX-1];
8673                 board[fromY][fromX-1] = EmptySquare;
8674         }else{  captured = board[fromY][fromX+1];
8675                 board[fromY][fromX+1] = EmptySquare;
8676         }
8677     } else {
8678         board[toY][toX] = board[fromY][fromX];
8679         board[fromY][fromX] = EmptySquare;
8680     }
8681   }
8682
8683     if (gameInfo.holdingsWidth != 0) {
8684
8685       /* !!A lot more code needs to be written to support holdings  */
8686       /* [HGM] OK, so I have written it. Holdings are stored in the */
8687       /* penultimate board files, so they are automaticlly stored   */
8688       /* in the game history.                                       */
8689       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8690                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8691         /* Delete from holdings, by decreasing count */
8692         /* and erasing image if necessary            */
8693         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8694         if(p < (int) BlackPawn) { /* white drop */
8695              p -= (int)WhitePawn;
8696                  p = PieceToNumber((ChessSquare)p);
8697              if(p >= gameInfo.holdingsSize) p = 0;
8698              if(--board[p][BOARD_WIDTH-2] <= 0)
8699                   board[p][BOARD_WIDTH-1] = EmptySquare;
8700              if((int)board[p][BOARD_WIDTH-2] < 0)
8701                         board[p][BOARD_WIDTH-2] = 0;
8702         } else {                  /* black drop */
8703              p -= (int)BlackPawn;
8704                  p = PieceToNumber((ChessSquare)p);
8705              if(p >= gameInfo.holdingsSize) p = 0;
8706              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8707                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8708              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8709                         board[BOARD_HEIGHT-1-p][1] = 0;
8710         }
8711       }
8712       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8713           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8714         /* [HGM] holdings: Add to holdings, if holdings exist */
8715         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8716                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8717                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8718         }
8719         p = (int) captured;
8720         if (p >= (int) BlackPawn) {
8721           p -= (int)BlackPawn;
8722           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8723                   /* in Shogi restore piece to its original  first */
8724                   captured = (ChessSquare) (DEMOTED captured);
8725                   p = DEMOTED p;
8726           }
8727           p = PieceToNumber((ChessSquare)p);
8728           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8729           board[p][BOARD_WIDTH-2]++;
8730           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8731         } else {
8732           p -= (int)WhitePawn;
8733           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8734                   captured = (ChessSquare) (DEMOTED captured);
8735                   p = DEMOTED p;
8736           }
8737           p = PieceToNumber((ChessSquare)p);
8738           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8739           board[BOARD_HEIGHT-1-p][1]++;
8740           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8741         }
8742       }
8743     } else if (gameInfo.variant == VariantAtomic) {
8744       if (captured != EmptySquare) {
8745         int y, x;
8746         for (y = toY-1; y <= toY+1; y++) {
8747           for (x = toX-1; x <= toX+1; x++) {
8748             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8749                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8750               board[y][x] = EmptySquare;
8751             }
8752           }
8753         }
8754         board[toY][toX] = EmptySquare;
8755       }
8756     }
8757     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8758         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8759     } else
8760     if(promoChar == '+') {
8761         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8762         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8763     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8764         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8765     }
8766     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8767                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8768         // [HGM] superchess: take promotion piece out of holdings
8769         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8770         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8771             if(!--board[k][BOARD_WIDTH-2])
8772                 board[k][BOARD_WIDTH-1] = EmptySquare;
8773         } else {
8774             if(!--board[BOARD_HEIGHT-1-k][1])
8775                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8776         }
8777     }
8778
8779 }
8780
8781 /* Updates forwardMostMove */
8782 void
8783 MakeMove(fromX, fromY, toX, toY, promoChar)
8784      int fromX, fromY, toX, toY;
8785      int promoChar;
8786 {
8787 //    forwardMostMove++; // [HGM] bare: moved downstream
8788
8789     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8790         int timeLeft; static int lastLoadFlag=0; int king, piece;
8791         piece = boards[forwardMostMove][fromY][fromX];
8792         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8793         if(gameInfo.variant == VariantKnightmate)
8794             king += (int) WhiteUnicorn - (int) WhiteKing;
8795         if(forwardMostMove == 0) {
8796             if(blackPlaysFirst)
8797                 fprintf(serverMoves, "%s;", second.tidy);
8798             fprintf(serverMoves, "%s;", first.tidy);
8799             if(!blackPlaysFirst)
8800                 fprintf(serverMoves, "%s;", second.tidy);
8801         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8802         lastLoadFlag = loadFlag;
8803         // print base move
8804         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8805         // print castling suffix
8806         if( toY == fromY && piece == king ) {
8807             if(toX-fromX > 1)
8808                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8809             if(fromX-toX >1)
8810                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8811         }
8812         // e.p. suffix
8813         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8814              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8815              boards[forwardMostMove][toY][toX] == EmptySquare
8816              && fromX != toX && fromY != toY)
8817                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8818         // promotion suffix
8819         if(promoChar != NULLCHAR)
8820                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8821         if(!loadFlag) {
8822             fprintf(serverMoves, "/%d/%d",
8823                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8824             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8825             else                      timeLeft = blackTimeRemaining/1000;
8826             fprintf(serverMoves, "/%d", timeLeft);
8827         }
8828         fflush(serverMoves);
8829     }
8830
8831     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8832       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8833                         0, 1);
8834       return;
8835     }
8836     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8837     if (commentList[forwardMostMove+1] != NULL) {
8838         free(commentList[forwardMostMove+1]);
8839         commentList[forwardMostMove+1] = NULL;
8840     }
8841     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8842     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8843     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8844     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8845     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8846     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8847     gameInfo.result = GameUnfinished;
8848     if (gameInfo.resultDetails != NULL) {
8849         free(gameInfo.resultDetails);
8850         gameInfo.resultDetails = NULL;
8851     }
8852     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8853                               moveList[forwardMostMove - 1]);
8854     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8855                              PosFlags(forwardMostMove - 1),
8856                              fromY, fromX, toY, toX, promoChar,
8857                              parseList[forwardMostMove - 1]);
8858     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8859       case MT_NONE:
8860       case MT_STALEMATE:
8861       default:
8862         break;
8863       case MT_CHECK:
8864         if(gameInfo.variant != VariantShogi)
8865             strcat(parseList[forwardMostMove - 1], "+");
8866         break;
8867       case MT_CHECKMATE:
8868       case MT_STAINMATE:
8869         strcat(parseList[forwardMostMove - 1], "#");
8870         break;
8871     }
8872     if (appData.debugMode) {
8873         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8874     }
8875
8876 }
8877
8878 /* Updates currentMove if not pausing */
8879 void
8880 ShowMove(fromX, fromY, toX, toY)
8881 {
8882     int instant = (gameMode == PlayFromGameFile) ?
8883         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8884     if(appData.noGUI) return;
8885     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8886         if (!instant) {
8887             if (forwardMostMove == currentMove + 1) {
8888                 AnimateMove(boards[forwardMostMove - 1],
8889                             fromX, fromY, toX, toY);
8890             }
8891             if (appData.highlightLastMove) {
8892                 SetHighlights(fromX, fromY, toX, toY);
8893             }
8894         }
8895         currentMove = forwardMostMove;
8896     }
8897
8898     if (instant) return;
8899
8900     DisplayMove(currentMove - 1);
8901     DrawPosition(FALSE, boards[currentMove]);
8902     DisplayBothClocks();
8903     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8904 }
8905
8906 void SendEgtPath(ChessProgramState *cps)
8907 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8908         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8909
8910         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8911
8912         while(*p) {
8913             char c, *q = name+1, *r, *s;
8914
8915             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8916             while(*p && *p != ',') *q++ = *p++;
8917             *q++ = ':'; *q = 0;
8918             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8919                 strcmp(name, ",nalimov:") == 0 ) {
8920                 // take nalimov path from the menu-changeable option first, if it is defined
8921               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8922                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8923             } else
8924             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8925                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8926                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8927                 s = r = StrStr(s, ":") + 1; // beginning of path info
8928                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8929                 c = *r; *r = 0;             // temporarily null-terminate path info
8930                     *--q = 0;               // strip of trailig ':' from name
8931                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8932                 *r = c;
8933                 SendToProgram(buf,cps);     // send egtbpath command for this format
8934             }
8935             if(*p == ',') p++; // read away comma to position for next format name
8936         }
8937 }
8938
8939 void
8940 InitChessProgram(cps, setup)
8941      ChessProgramState *cps;
8942      int setup; /* [HGM] needed to setup FRC opening position */
8943 {
8944     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8945     if (appData.noChessProgram) return;
8946     hintRequested = FALSE;
8947     bookRequested = FALSE;
8948
8949     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8950     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8951     if(cps->memSize) { /* [HGM] memory */
8952       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8953         SendToProgram(buf, cps);
8954     }
8955     SendEgtPath(cps); /* [HGM] EGT */
8956     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8957       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8958         SendToProgram(buf, cps);
8959     }
8960
8961     SendToProgram(cps->initString, cps);
8962     if (gameInfo.variant != VariantNormal &&
8963         gameInfo.variant != VariantLoadable
8964         /* [HGM] also send variant if board size non-standard */
8965         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8966                                             ) {
8967       char *v = VariantName(gameInfo.variant);
8968       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8969         /* [HGM] in protocol 1 we have to assume all variants valid */
8970         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8971         DisplayFatalError(buf, 0, 1);
8972         return;
8973       }
8974
8975       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8976       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8977       if( gameInfo.variant == VariantXiangqi )
8978            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8979       if( gameInfo.variant == VariantShogi )
8980            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8981       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8982            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8983       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8984           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8985            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8986       if( gameInfo.variant == VariantCourier )
8987            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8988       if( gameInfo.variant == VariantSuper )
8989            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8990       if( gameInfo.variant == VariantGreat )
8991            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8992       if( gameInfo.variant == VariantSChess )
8993            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8994
8995       if(overruled) {
8996         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8997                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8998            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8999            if(StrStr(cps->variants, b) == NULL) {
9000                // specific sized variant not known, check if general sizing allowed
9001                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9002                    if(StrStr(cps->variants, "boardsize") == NULL) {
9003                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9004                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9005                        DisplayFatalError(buf, 0, 1);
9006                        return;
9007                    }
9008                    /* [HGM] here we really should compare with the maximum supported board size */
9009                }
9010            }
9011       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9012       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9013       SendToProgram(buf, cps);
9014     }
9015     currentlyInitializedVariant = gameInfo.variant;
9016
9017     /* [HGM] send opening position in FRC to first engine */
9018     if(setup) {
9019           SendToProgram("force\n", cps);
9020           SendBoard(cps, 0);
9021           /* engine is now in force mode! Set flag to wake it up after first move. */
9022           setboardSpoiledMachineBlack = 1;
9023     }
9024
9025     if (cps->sendICS) {
9026       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9027       SendToProgram(buf, cps);
9028     }
9029     cps->maybeThinking = FALSE;
9030     cps->offeredDraw = 0;
9031     if (!appData.icsActive) {
9032         SendTimeControl(cps, movesPerSession, timeControl,
9033                         timeIncrement, appData.searchDepth,
9034                         searchTime);
9035     }
9036     if (appData.showThinking
9037         // [HGM] thinking: four options require thinking output to be sent
9038         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9039                                 ) {
9040         SendToProgram("post\n", cps);
9041     }
9042     SendToProgram("hard\n", cps);
9043     if (!appData.ponderNextMove) {
9044         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9045            it without being sure what state we are in first.  "hard"
9046            is not a toggle, so that one is OK.
9047          */
9048         SendToProgram("easy\n", cps);
9049     }
9050     if (cps->usePing) {
9051       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9052       SendToProgram(buf, cps);
9053     }
9054     cps->initDone = TRUE;
9055 }
9056
9057
9058 void
9059 StartChessProgram(cps)
9060      ChessProgramState *cps;
9061 {
9062     char buf[MSG_SIZ];
9063     int err;
9064
9065     if (appData.noChessProgram) return;
9066     cps->initDone = FALSE;
9067
9068     if (strcmp(cps->host, "localhost") == 0) {
9069         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9070     } else if (*appData.remoteShell == NULLCHAR) {
9071         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9072     } else {
9073         if (*appData.remoteUser == NULLCHAR) {
9074           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9075                     cps->program);
9076         } else {
9077           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9078                     cps->host, appData.remoteUser, cps->program);
9079         }
9080         err = StartChildProcess(buf, "", &cps->pr);
9081     }
9082
9083     if (err != 0) {
9084       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9085         DisplayFatalError(buf, err, 1);
9086         cps->pr = NoProc;
9087         cps->isr = NULL;
9088         return;
9089     }
9090
9091     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9092     if (cps->protocolVersion > 1) {
9093       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9094       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9095       cps->comboCnt = 0;  //                and values of combo boxes
9096       SendToProgram(buf, cps);
9097     } else {
9098       SendToProgram("xboard\n", cps);
9099     }
9100 }
9101
9102
9103 void
9104 TwoMachinesEventIfReady P((void))
9105 {
9106   if (first.lastPing != first.lastPong) {
9107     DisplayMessage("", _("Waiting for first chess program"));
9108     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9109     return;
9110   }
9111   if (second.lastPing != second.lastPong) {
9112     DisplayMessage("", _("Waiting for second chess program"));
9113     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9114     return;
9115   }
9116   ThawUI();
9117   TwoMachinesEvent();
9118 }
9119
9120 void
9121 NextMatchGame P((void))
9122 {
9123     int index; /* [HGM] autoinc: step load index during match */
9124     Reset(FALSE, TRUE);
9125     if (*appData.loadGameFile != NULLCHAR) {
9126         index = appData.loadGameIndex;
9127         if(index < 0) { // [HGM] autoinc
9128             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9129             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9130         }
9131         LoadGameFromFile(appData.loadGameFile,
9132                          index,
9133                          appData.loadGameFile, FALSE);
9134     } else if (*appData.loadPositionFile != NULLCHAR) {
9135         index = appData.loadPositionIndex;
9136         if(index < 0) { // [HGM] autoinc
9137             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9138             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9139         }
9140         LoadPositionFromFile(appData.loadPositionFile,
9141                              index,
9142                              appData.loadPositionFile);
9143     }
9144     TwoMachinesEventIfReady();
9145 }
9146
9147 void UserAdjudicationEvent( int result )
9148 {
9149     ChessMove gameResult = GameIsDrawn;
9150
9151     if( result > 0 ) {
9152         gameResult = WhiteWins;
9153     }
9154     else if( result < 0 ) {
9155         gameResult = BlackWins;
9156     }
9157
9158     if( gameMode == TwoMachinesPlay ) {
9159         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9160     }
9161 }
9162
9163
9164 // [HGM] save: calculate checksum of game to make games easily identifiable
9165 int StringCheckSum(char *s)
9166 {
9167         int i = 0;
9168         if(s==NULL) return 0;
9169         while(*s) i = i*259 + *s++;
9170         return i;
9171 }
9172
9173 int GameCheckSum()
9174 {
9175         int i, sum=0;
9176         for(i=backwardMostMove; i<forwardMostMove; i++) {
9177                 sum += pvInfoList[i].depth;
9178                 sum += StringCheckSum(parseList[i]);
9179                 sum += StringCheckSum(commentList[i]);
9180                 sum *= 261;
9181         }
9182         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9183         return sum + StringCheckSum(commentList[i]);
9184 } // end of save patch
9185
9186 void
9187 GameEnds(result, resultDetails, whosays)
9188      ChessMove result;
9189      char *resultDetails;
9190      int whosays;
9191 {
9192     GameMode nextGameMode;
9193     int isIcsGame;
9194     char buf[MSG_SIZ], popupRequested = 0;
9195
9196     if(endingGame) return; /* [HGM] crash: forbid recursion */
9197     endingGame = 1;
9198     if(twoBoards) { // [HGM] dual: switch back to one board
9199         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9200         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9201     }
9202     if (appData.debugMode) {
9203       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9204               result, resultDetails ? resultDetails : "(null)", whosays);
9205     }
9206
9207     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9208
9209     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9210         /* If we are playing on ICS, the server decides when the
9211            game is over, but the engine can offer to draw, claim
9212            a draw, or resign.
9213          */
9214 #if ZIPPY
9215         if (appData.zippyPlay && first.initDone) {
9216             if (result == GameIsDrawn) {
9217                 /* In case draw still needs to be claimed */
9218                 SendToICS(ics_prefix);
9219                 SendToICS("draw\n");
9220             } else if (StrCaseStr(resultDetails, "resign")) {
9221                 SendToICS(ics_prefix);
9222                 SendToICS("resign\n");
9223             }
9224         }
9225 #endif
9226         endingGame = 0; /* [HGM] crash */
9227         return;
9228     }
9229
9230     /* If we're loading the game from a file, stop */
9231     if (whosays == GE_FILE) {
9232       (void) StopLoadGameTimer();
9233       gameFileFP = NULL;
9234     }
9235
9236     /* Cancel draw offers */
9237     first.offeredDraw = second.offeredDraw = 0;
9238
9239     /* If this is an ICS game, only ICS can really say it's done;
9240        if not, anyone can. */
9241     isIcsGame = (gameMode == IcsPlayingWhite ||
9242                  gameMode == IcsPlayingBlack ||
9243                  gameMode == IcsObserving    ||
9244                  gameMode == IcsExamining);
9245
9246     if (!isIcsGame || whosays == GE_ICS) {
9247         /* OK -- not an ICS game, or ICS said it was done */
9248         StopClocks();
9249         if (!isIcsGame && !appData.noChessProgram)
9250           SetUserThinkingEnables();
9251
9252         /* [HGM] if a machine claims the game end we verify this claim */
9253         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9254             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9255                 char claimer;
9256                 ChessMove trueResult = (ChessMove) -1;
9257
9258                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9259                                             first.twoMachinesColor[0] :
9260                                             second.twoMachinesColor[0] ;
9261
9262                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9263                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9264                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9265                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9266                 } else
9267                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9268                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9269                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9270                 } else
9271                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9272                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9273                 }
9274
9275                 // now verify win claims, but not in drop games, as we don't understand those yet
9276                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9277                                                  || gameInfo.variant == VariantGreat) &&
9278                     (result == WhiteWins && claimer == 'w' ||
9279                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9280                       if (appData.debugMode) {
9281                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9282                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9283                       }
9284                       if(result != trueResult) {
9285                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9286                               result = claimer == 'w' ? BlackWins : WhiteWins;
9287                               resultDetails = buf;
9288                       }
9289                 } else
9290                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9291                     && (forwardMostMove <= backwardMostMove ||
9292                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9293                         (claimer=='b')==(forwardMostMove&1))
9294                                                                                   ) {
9295                       /* [HGM] verify: draws that were not flagged are false claims */
9296                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9297                       result = claimer == 'w' ? BlackWins : WhiteWins;
9298                       resultDetails = buf;
9299                 }
9300                 /* (Claiming a loss is accepted no questions asked!) */
9301             }
9302             /* [HGM] bare: don't allow bare King to win */
9303             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9304                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9305                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9306                && result != GameIsDrawn)
9307             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9308                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9309                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9310                         if(p >= 0 && p <= (int)WhiteKing) k++;
9311                 }
9312                 if (appData.debugMode) {
9313                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9314                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9315                 }
9316                 if(k <= 1) {
9317                         result = GameIsDrawn;
9318                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9319                         resultDetails = buf;
9320                 }
9321             }
9322         }
9323
9324
9325         if(serverMoves != NULL && !loadFlag) { char c = '=';
9326             if(result==WhiteWins) c = '+';
9327             if(result==BlackWins) c = '-';
9328             if(resultDetails != NULL)
9329                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9330         }
9331         if (resultDetails != NULL) {
9332             gameInfo.result = result;
9333             gameInfo.resultDetails = StrSave(resultDetails);
9334
9335             /* display last move only if game was not loaded from file */
9336             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9337                 DisplayMove(currentMove - 1);
9338
9339             if (forwardMostMove != 0) {
9340                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9341                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9342                                                                 ) {
9343                     if (*appData.saveGameFile != NULLCHAR) {
9344                         SaveGameToFile(appData.saveGameFile, TRUE);
9345                     } else if (appData.autoSaveGames) {
9346                         AutoSaveGame();
9347                     }
9348                     if (*appData.savePositionFile != NULLCHAR) {
9349                         SavePositionToFile(appData.savePositionFile);
9350                     }
9351                 }
9352             }
9353
9354             /* Tell program how game ended in case it is learning */
9355             /* [HGM] Moved this to after saving the PGN, just in case */
9356             /* engine died and we got here through time loss. In that */
9357             /* case we will get a fatal error writing the pipe, which */
9358             /* would otherwise lose us the PGN.                       */
9359             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9360             /* output during GameEnds should never be fatal anymore   */
9361             if (gameMode == MachinePlaysWhite ||
9362                 gameMode == MachinePlaysBlack ||
9363                 gameMode == TwoMachinesPlay ||
9364                 gameMode == IcsPlayingWhite ||
9365                 gameMode == IcsPlayingBlack ||
9366                 gameMode == BeginningOfGame) {
9367                 char buf[MSG_SIZ];
9368                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9369                         resultDetails);
9370                 if (first.pr != NoProc) {
9371                     SendToProgram(buf, &first);
9372                 }
9373                 if (second.pr != NoProc &&
9374                     gameMode == TwoMachinesPlay) {
9375                     SendToProgram(buf, &second);
9376                 }
9377             }
9378         }
9379
9380         if (appData.icsActive) {
9381             if (appData.quietPlay &&
9382                 (gameMode == IcsPlayingWhite ||
9383                  gameMode == IcsPlayingBlack)) {
9384                 SendToICS(ics_prefix);
9385                 SendToICS("set shout 1\n");
9386             }
9387             nextGameMode = IcsIdle;
9388             ics_user_moved = FALSE;
9389             /* clean up premove.  It's ugly when the game has ended and the
9390              * premove highlights are still on the board.
9391              */
9392             if (gotPremove) {
9393               gotPremove = FALSE;
9394               ClearPremoveHighlights();
9395               DrawPosition(FALSE, boards[currentMove]);
9396             }
9397             if (whosays == GE_ICS) {
9398                 switch (result) {
9399                 case WhiteWins:
9400                     if (gameMode == IcsPlayingWhite)
9401                         PlayIcsWinSound();
9402                     else if(gameMode == IcsPlayingBlack)
9403                         PlayIcsLossSound();
9404                     break;
9405                 case BlackWins:
9406                     if (gameMode == IcsPlayingBlack)
9407                         PlayIcsWinSound();
9408                     else if(gameMode == IcsPlayingWhite)
9409                         PlayIcsLossSound();
9410                     break;
9411                 case GameIsDrawn:
9412                     PlayIcsDrawSound();
9413                     break;
9414                 default:
9415                     PlayIcsUnfinishedSound();
9416                 }
9417             }
9418         } else if (gameMode == EditGame ||
9419                    gameMode == PlayFromGameFile ||
9420                    gameMode == AnalyzeMode ||
9421                    gameMode == AnalyzeFile) {
9422             nextGameMode = gameMode;
9423         } else {
9424             nextGameMode = EndOfGame;
9425         }
9426         pausing = FALSE;
9427         ModeHighlight();
9428     } else {
9429         nextGameMode = gameMode;
9430     }
9431
9432     if (appData.noChessProgram) {
9433         gameMode = nextGameMode;
9434         ModeHighlight();
9435         endingGame = 0; /* [HGM] crash */
9436         return;
9437     }
9438
9439     if (first.reuse) {
9440         /* Put first chess program into idle state */
9441         if (first.pr != NoProc &&
9442             (gameMode == MachinePlaysWhite ||
9443              gameMode == MachinePlaysBlack ||
9444              gameMode == TwoMachinesPlay ||
9445              gameMode == IcsPlayingWhite ||
9446              gameMode == IcsPlayingBlack ||
9447              gameMode == BeginningOfGame)) {
9448             SendToProgram("force\n", &first);
9449             if (first.usePing) {
9450               char buf[MSG_SIZ];
9451               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9452               SendToProgram(buf, &first);
9453             }
9454         }
9455     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9456         /* Kill off first chess program */
9457         if (first.isr != NULL)
9458           RemoveInputSource(first.isr);
9459         first.isr = NULL;
9460
9461         if (first.pr != NoProc) {
9462             ExitAnalyzeMode();
9463             DoSleep( appData.delayBeforeQuit );
9464             SendToProgram("quit\n", &first);
9465             DoSleep( appData.delayAfterQuit );
9466             DestroyChildProcess(first.pr, first.useSigterm);
9467         }
9468         first.pr = NoProc;
9469     }
9470     if (second.reuse) {
9471         /* Put second chess program into idle state */
9472         if (second.pr != NoProc &&
9473             gameMode == TwoMachinesPlay) {
9474             SendToProgram("force\n", &second);
9475             if (second.usePing) {
9476               char buf[MSG_SIZ];
9477               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9478               SendToProgram(buf, &second);
9479             }
9480         }
9481     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9482         /* Kill off second chess program */
9483         if (second.isr != NULL)
9484           RemoveInputSource(second.isr);
9485         second.isr = NULL;
9486
9487         if (second.pr != NoProc) {
9488             DoSleep( appData.delayBeforeQuit );
9489             SendToProgram("quit\n", &second);
9490             DoSleep( appData.delayAfterQuit );
9491             DestroyChildProcess(second.pr, second.useSigterm);
9492         }
9493         second.pr = NoProc;
9494     }
9495
9496     if (matchMode && gameMode == TwoMachinesPlay) {
9497         switch (result) {
9498         case WhiteWins:
9499           if (first.twoMachinesColor[0] == 'w') {
9500             first.matchWins++;
9501           } else {
9502             second.matchWins++;
9503           }
9504           break;
9505         case BlackWins:
9506           if (first.twoMachinesColor[0] == 'b') {
9507             first.matchWins++;
9508           } else {
9509             second.matchWins++;
9510           }
9511           break;
9512         default:
9513           break;
9514         }
9515         if (matchGame < appData.matchGames) {
9516             char *tmp;
9517             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9518                 tmp = first.twoMachinesColor;
9519                 first.twoMachinesColor = second.twoMachinesColor;
9520                 second.twoMachinesColor = tmp;
9521             }
9522             gameMode = nextGameMode;
9523             matchGame++;
9524             if(appData.matchPause>10000 || appData.matchPause<10)
9525                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9526             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9527             endingGame = 0; /* [HGM] crash */
9528             return;
9529         } else {
9530             gameMode = nextGameMode;
9531             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9532                      first.tidy, second.tidy,
9533                      first.matchWins, second.matchWins,
9534                      appData.matchGames - (first.matchWins + second.matchWins));
9535             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9536             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9537                 first.twoMachinesColor = "black\n";
9538                 second.twoMachinesColor = "white\n";
9539             } else {
9540                 first.twoMachinesColor = "white\n";
9541                 second.twoMachinesColor = "black\n";
9542             }
9543         }
9544     }
9545     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9546         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9547       ExitAnalyzeMode();
9548     gameMode = nextGameMode;
9549     ModeHighlight();
9550     endingGame = 0;  /* [HGM] crash */
9551     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9552       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9553         matchMode = FALSE; appData.matchGames = matchGame = 0;
9554         DisplayNote(buf);
9555       }
9556     }
9557 }
9558
9559 /* Assumes program was just initialized (initString sent).
9560    Leaves program in force mode. */
9561 void
9562 FeedMovesToProgram(cps, upto)
9563      ChessProgramState *cps;
9564      int upto;
9565 {
9566     int i;
9567
9568     if (appData.debugMode)
9569       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9570               startedFromSetupPosition ? "position and " : "",
9571               backwardMostMove, upto, cps->which);
9572     if(currentlyInitializedVariant != gameInfo.variant) {
9573       char buf[MSG_SIZ];
9574         // [HGM] variantswitch: make engine aware of new variant
9575         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9576                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9577         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9578         SendToProgram(buf, cps);
9579         currentlyInitializedVariant = gameInfo.variant;
9580     }
9581     SendToProgram("force\n", cps);
9582     if (startedFromSetupPosition) {
9583         SendBoard(cps, backwardMostMove);
9584     if (appData.debugMode) {
9585         fprintf(debugFP, "feedMoves\n");
9586     }
9587     }
9588     for (i = backwardMostMove; i < upto; i++) {
9589         SendMoveToProgram(i, cps);
9590     }
9591 }
9592
9593
9594 void
9595 ResurrectChessProgram()
9596 {
9597      /* The chess program may have exited.
9598         If so, restart it and feed it all the moves made so far. */
9599
9600     if (appData.noChessProgram || first.pr != NoProc) return;
9601
9602     StartChessProgram(&first);
9603     InitChessProgram(&first, FALSE);
9604     FeedMovesToProgram(&first, currentMove);
9605
9606     if (!first.sendTime) {
9607         /* can't tell gnuchess what its clock should read,
9608            so we bow to its notion. */
9609         ResetClocks();
9610         timeRemaining[0][currentMove] = whiteTimeRemaining;
9611         timeRemaining[1][currentMove] = blackTimeRemaining;
9612     }
9613
9614     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9615                 appData.icsEngineAnalyze) && first.analysisSupport) {
9616       SendToProgram("analyze\n", &first);
9617       first.analyzing = TRUE;
9618     }
9619 }
9620
9621 /*
9622  * Button procedures
9623  */
9624 void
9625 Reset(redraw, init)
9626      int redraw, init;
9627 {
9628     int i;
9629
9630     if (appData.debugMode) {
9631         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9632                 redraw, init, gameMode);
9633     }
9634     CleanupTail(); // [HGM] vari: delete any stored variations
9635     pausing = pauseExamInvalid = FALSE;
9636     startedFromSetupPosition = blackPlaysFirst = FALSE;
9637     firstMove = TRUE;
9638     whiteFlag = blackFlag = FALSE;
9639     userOfferedDraw = FALSE;
9640     hintRequested = bookRequested = FALSE;
9641     first.maybeThinking = FALSE;
9642     second.maybeThinking = FALSE;
9643     first.bookSuspend = FALSE; // [HGM] book
9644     second.bookSuspend = FALSE;
9645     thinkOutput[0] = NULLCHAR;
9646     lastHint[0] = NULLCHAR;
9647     ClearGameInfo(&gameInfo);
9648     gameInfo.variant = StringToVariant(appData.variant);
9649     ics_user_moved = ics_clock_paused = FALSE;
9650     ics_getting_history = H_FALSE;
9651     ics_gamenum = -1;
9652     white_holding[0] = black_holding[0] = NULLCHAR;
9653     ClearProgramStats();
9654     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9655
9656     ResetFrontEnd();
9657     ClearHighlights();
9658     flipView = appData.flipView;
9659     ClearPremoveHighlights();
9660     gotPremove = FALSE;
9661     alarmSounded = FALSE;
9662
9663     GameEnds(EndOfFile, NULL, GE_PLAYER);
9664     if(appData.serverMovesName != NULL) {
9665         /* [HGM] prepare to make moves file for broadcasting */
9666         clock_t t = clock();
9667         if(serverMoves != NULL) fclose(serverMoves);
9668         serverMoves = fopen(appData.serverMovesName, "r");
9669         if(serverMoves != NULL) {
9670             fclose(serverMoves);
9671             /* delay 15 sec before overwriting, so all clients can see end */
9672             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9673         }
9674         serverMoves = fopen(appData.serverMovesName, "w");
9675     }
9676
9677     ExitAnalyzeMode();
9678     gameMode = BeginningOfGame;
9679     ModeHighlight();
9680     if(appData.icsActive) gameInfo.variant = VariantNormal;
9681     currentMove = forwardMostMove = backwardMostMove = 0;
9682     InitPosition(redraw);
9683     for (i = 0; i < MAX_MOVES; i++) {
9684         if (commentList[i] != NULL) {
9685             free(commentList[i]);
9686             commentList[i] = NULL;
9687         }
9688     }
9689     ResetClocks();
9690     timeRemaining[0][0] = whiteTimeRemaining;
9691     timeRemaining[1][0] = blackTimeRemaining;
9692     if (first.pr == NULL) {
9693         StartChessProgram(&first);
9694     }
9695     if (init) {
9696             InitChessProgram(&first, startedFromSetupPosition);
9697     }
9698     DisplayTitle("");
9699     DisplayMessage("", "");
9700     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9701     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9702 }
9703
9704 void
9705 AutoPlayGameLoop()
9706 {
9707     for (;;) {
9708         if (!AutoPlayOneMove())
9709           return;
9710         if (matchMode || appData.timeDelay == 0)
9711           continue;
9712         if (appData.timeDelay < 0)
9713           return;
9714         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9715         break;
9716     }
9717 }
9718
9719
9720 int
9721 AutoPlayOneMove()
9722 {
9723     int fromX, fromY, toX, toY;
9724
9725     if (appData.debugMode) {
9726       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9727     }
9728
9729     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9730       return FALSE;
9731
9732     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9733       pvInfoList[currentMove].depth = programStats.depth;
9734       pvInfoList[currentMove].score = programStats.score;
9735       pvInfoList[currentMove].time  = 0;
9736       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9737     }
9738
9739     if (currentMove >= forwardMostMove) {
9740       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9741       gameMode = EditGame;
9742       ModeHighlight();
9743
9744       /* [AS] Clear current move marker at the end of a game */
9745       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9746
9747       return FALSE;
9748     }
9749
9750     toX = moveList[currentMove][2] - AAA;
9751     toY = moveList[currentMove][3] - ONE;
9752
9753     if (moveList[currentMove][1] == '@') {
9754         if (appData.highlightLastMove) {
9755             SetHighlights(-1, -1, toX, toY);
9756         }
9757     } else {
9758         fromX = moveList[currentMove][0] - AAA;
9759         fromY = moveList[currentMove][1] - ONE;
9760
9761         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9762
9763         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9764
9765         if (appData.highlightLastMove) {
9766             SetHighlights(fromX, fromY, toX, toY);
9767         }
9768     }
9769     DisplayMove(currentMove);
9770     SendMoveToProgram(currentMove++, &first);
9771     DisplayBothClocks();
9772     DrawPosition(FALSE, boards[currentMove]);
9773     // [HGM] PV info: always display, routine tests if empty
9774     DisplayComment(currentMove - 1, commentList[currentMove]);
9775     return TRUE;
9776 }
9777
9778
9779 int
9780 LoadGameOneMove(readAhead)
9781      ChessMove readAhead;
9782 {
9783     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9784     char promoChar = NULLCHAR;
9785     ChessMove moveType;
9786     char move[MSG_SIZ];
9787     char *p, *q;
9788
9789     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9790         gameMode != AnalyzeMode && gameMode != Training) {
9791         gameFileFP = NULL;
9792         return FALSE;
9793     }
9794
9795     yyboardindex = forwardMostMove;
9796     if (readAhead != EndOfFile) {
9797       moveType = readAhead;
9798     } else {
9799       if (gameFileFP == NULL)
9800           return FALSE;
9801       moveType = (ChessMove) Myylex();
9802     }
9803
9804     done = FALSE;
9805     switch (moveType) {
9806       case Comment:
9807         if (appData.debugMode)
9808           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9809         p = yy_text;
9810
9811         /* append the comment but don't display it */
9812         AppendComment(currentMove, p, FALSE);
9813         return TRUE;
9814
9815       case WhiteCapturesEnPassant:
9816       case BlackCapturesEnPassant:
9817       case WhitePromotion:
9818       case BlackPromotion:
9819       case WhiteNonPromotion:
9820       case BlackNonPromotion:
9821       case NormalMove:
9822       case WhiteKingSideCastle:
9823       case WhiteQueenSideCastle:
9824       case BlackKingSideCastle:
9825       case BlackQueenSideCastle:
9826       case WhiteKingSideCastleWild:
9827       case WhiteQueenSideCastleWild:
9828       case BlackKingSideCastleWild:
9829       case BlackQueenSideCastleWild:
9830       /* PUSH Fabien */
9831       case WhiteHSideCastleFR:
9832       case WhiteASideCastleFR:
9833       case BlackHSideCastleFR:
9834       case BlackASideCastleFR:
9835       /* POP Fabien */
9836         if (appData.debugMode)
9837           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9838         fromX = currentMoveString[0] - AAA;
9839         fromY = currentMoveString[1] - ONE;
9840         toX = currentMoveString[2] - AAA;
9841         toY = currentMoveString[3] - ONE;
9842         promoChar = currentMoveString[4];
9843         break;
9844
9845       case WhiteDrop:
9846       case BlackDrop:
9847         if (appData.debugMode)
9848           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9849         fromX = moveType == WhiteDrop ?
9850           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9851         (int) CharToPiece(ToLower(currentMoveString[0]));
9852         fromY = DROP_RANK;
9853         toX = currentMoveString[2] - AAA;
9854         toY = currentMoveString[3] - ONE;
9855         break;
9856
9857       case WhiteWins:
9858       case BlackWins:
9859       case GameIsDrawn:
9860       case GameUnfinished:
9861         if (appData.debugMode)
9862           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9863         p = strchr(yy_text, '{');
9864         if (p == NULL) p = strchr(yy_text, '(');
9865         if (p == NULL) {
9866             p = yy_text;
9867             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9868         } else {
9869             q = strchr(p, *p == '{' ? '}' : ')');
9870             if (q != NULL) *q = NULLCHAR;
9871             p++;
9872         }
9873         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9874         GameEnds(moveType, p, GE_FILE);
9875         done = TRUE;
9876         if (cmailMsgLoaded) {
9877             ClearHighlights();
9878             flipView = WhiteOnMove(currentMove);
9879             if (moveType == GameUnfinished) flipView = !flipView;
9880             if (appData.debugMode)
9881               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9882         }
9883         break;
9884
9885       case EndOfFile:
9886         if (appData.debugMode)
9887           fprintf(debugFP, "Parser hit end of file\n");
9888         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9889           case MT_NONE:
9890           case MT_CHECK:
9891             break;
9892           case MT_CHECKMATE:
9893           case MT_STAINMATE:
9894             if (WhiteOnMove(currentMove)) {
9895                 GameEnds(BlackWins, "Black mates", GE_FILE);
9896             } else {
9897                 GameEnds(WhiteWins, "White mates", GE_FILE);
9898             }
9899             break;
9900           case MT_STALEMATE:
9901             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9902             break;
9903         }
9904         done = TRUE;
9905         break;
9906
9907       case MoveNumberOne:
9908         if (lastLoadGameStart == GNUChessGame) {
9909             /* GNUChessGames have numbers, but they aren't move numbers */
9910             if (appData.debugMode)
9911               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9912                       yy_text, (int) moveType);
9913             return LoadGameOneMove(EndOfFile); /* tail recursion */
9914         }
9915         /* else fall thru */
9916
9917       case XBoardGame:
9918       case GNUChessGame:
9919       case PGNTag:
9920         /* Reached start of next game in file */
9921         if (appData.debugMode)
9922           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9923         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9924           case MT_NONE:
9925           case MT_CHECK:
9926             break;
9927           case MT_CHECKMATE:
9928           case MT_STAINMATE:
9929             if (WhiteOnMove(currentMove)) {
9930                 GameEnds(BlackWins, "Black mates", GE_FILE);
9931             } else {
9932                 GameEnds(WhiteWins, "White mates", GE_FILE);
9933             }
9934             break;
9935           case MT_STALEMATE:
9936             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9937             break;
9938         }
9939         done = TRUE;
9940         break;
9941
9942       case PositionDiagram:     /* should not happen; ignore */
9943       case ElapsedTime:         /* ignore */
9944       case NAG:                 /* ignore */
9945         if (appData.debugMode)
9946           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9947                   yy_text, (int) moveType);
9948         return LoadGameOneMove(EndOfFile); /* tail recursion */
9949
9950       case IllegalMove:
9951         if (appData.testLegality) {
9952             if (appData.debugMode)
9953               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9954             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9955                     (forwardMostMove / 2) + 1,
9956                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9957             DisplayError(move, 0);
9958             done = TRUE;
9959         } else {
9960             if (appData.debugMode)
9961               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9962                       yy_text, currentMoveString);
9963             fromX = currentMoveString[0] - AAA;
9964             fromY = currentMoveString[1] - ONE;
9965             toX = currentMoveString[2] - AAA;
9966             toY = currentMoveString[3] - ONE;
9967             promoChar = currentMoveString[4];
9968         }
9969         break;
9970
9971       case AmbiguousMove:
9972         if (appData.debugMode)
9973           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9974         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9975                 (forwardMostMove / 2) + 1,
9976                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9977         DisplayError(move, 0);
9978         done = TRUE;
9979         break;
9980
9981       default:
9982       case ImpossibleMove:
9983         if (appData.debugMode)
9984           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9985         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9986                 (forwardMostMove / 2) + 1,
9987                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9988         DisplayError(move, 0);
9989         done = TRUE;
9990         break;
9991     }
9992
9993     if (done) {
9994         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9995             DrawPosition(FALSE, boards[currentMove]);
9996             DisplayBothClocks();
9997             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9998               DisplayComment(currentMove - 1, commentList[currentMove]);
9999         }
10000         (void) StopLoadGameTimer();
10001         gameFileFP = NULL;
10002         cmailOldMove = forwardMostMove;
10003         return FALSE;
10004     } else {
10005         /* currentMoveString is set as a side-effect of yylex */
10006
10007         thinkOutput[0] = NULLCHAR;
10008         MakeMove(fromX, fromY, toX, toY, promoChar);
10009         currentMove = forwardMostMove;
10010         return TRUE;
10011     }
10012 }
10013
10014 /* Load the nth game from the given file */
10015 int
10016 LoadGameFromFile(filename, n, title, useList)
10017      char *filename;
10018      int n;
10019      char *title;
10020      /*Boolean*/ int useList;
10021 {
10022     FILE *f;
10023     char buf[MSG_SIZ];
10024
10025     if (strcmp(filename, "-") == 0) {
10026         f = stdin;
10027         title = "stdin";
10028     } else {
10029         f = fopen(filename, "rb");
10030         if (f == NULL) {
10031           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10032             DisplayError(buf, errno);
10033             return FALSE;
10034         }
10035     }
10036     if (fseek(f, 0, 0) == -1) {
10037         /* f is not seekable; probably a pipe */
10038         useList = FALSE;
10039     }
10040     if (useList && n == 0) {
10041         int error = GameListBuild(f);
10042         if (error) {
10043             DisplayError(_("Cannot build game list"), error);
10044         } else if (!ListEmpty(&gameList) &&
10045                    ((ListGame *) gameList.tailPred)->number > 1) {
10046             GameListPopUp(f, title);
10047             return TRUE;
10048         }
10049         GameListDestroy();
10050         n = 1;
10051     }
10052     if (n == 0) n = 1;
10053     return LoadGame(f, n, title, FALSE);
10054 }
10055
10056
10057 void
10058 MakeRegisteredMove()
10059 {
10060     int fromX, fromY, toX, toY;
10061     char promoChar;
10062     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10063         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10064           case CMAIL_MOVE:
10065           case CMAIL_DRAW:
10066             if (appData.debugMode)
10067               fprintf(debugFP, "Restoring %s for game %d\n",
10068                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10069
10070             thinkOutput[0] = NULLCHAR;
10071             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10072             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10073             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10074             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10075             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10076             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10077             MakeMove(fromX, fromY, toX, toY, promoChar);
10078             ShowMove(fromX, fromY, toX, toY);
10079
10080             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10081               case MT_NONE:
10082               case MT_CHECK:
10083                 break;
10084
10085               case MT_CHECKMATE:
10086               case MT_STAINMATE:
10087                 if (WhiteOnMove(currentMove)) {
10088                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10089                 } else {
10090                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10091                 }
10092                 break;
10093
10094               case MT_STALEMATE:
10095                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10096                 break;
10097             }
10098
10099             break;
10100
10101           case CMAIL_RESIGN:
10102             if (WhiteOnMove(currentMove)) {
10103                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10104             } else {
10105                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10106             }
10107             break;
10108
10109           case CMAIL_ACCEPT:
10110             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10111             break;
10112
10113           default:
10114             break;
10115         }
10116     }
10117
10118     return;
10119 }
10120
10121 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10122 int
10123 CmailLoadGame(f, gameNumber, title, useList)
10124      FILE *f;
10125      int gameNumber;
10126      char *title;
10127      int useList;
10128 {
10129     int retVal;
10130
10131     if (gameNumber > nCmailGames) {
10132         DisplayError(_("No more games in this message"), 0);
10133         return FALSE;
10134     }
10135     if (f == lastLoadGameFP) {
10136         int offset = gameNumber - lastLoadGameNumber;
10137         if (offset == 0) {
10138             cmailMsg[0] = NULLCHAR;
10139             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10140                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10141                 nCmailMovesRegistered--;
10142             }
10143             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10144             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10145                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10146             }
10147         } else {
10148             if (! RegisterMove()) return FALSE;
10149         }
10150     }
10151
10152     retVal = LoadGame(f, gameNumber, title, useList);
10153
10154     /* Make move registered during previous look at this game, if any */
10155     MakeRegisteredMove();
10156
10157     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10158         commentList[currentMove]
10159           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10160         DisplayComment(currentMove - 1, commentList[currentMove]);
10161     }
10162
10163     return retVal;
10164 }
10165
10166 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10167 int
10168 ReloadGame(offset)
10169      int offset;
10170 {
10171     int gameNumber = lastLoadGameNumber + offset;
10172     if (lastLoadGameFP == NULL) {
10173         DisplayError(_("No game has been loaded yet"), 0);
10174         return FALSE;
10175     }
10176     if (gameNumber <= 0) {
10177         DisplayError(_("Can't back up any further"), 0);
10178         return FALSE;
10179     }
10180     if (cmailMsgLoaded) {
10181         return CmailLoadGame(lastLoadGameFP, gameNumber,
10182                              lastLoadGameTitle, lastLoadGameUseList);
10183     } else {
10184         return LoadGame(lastLoadGameFP, gameNumber,
10185                         lastLoadGameTitle, lastLoadGameUseList);
10186     }
10187 }
10188
10189
10190
10191 /* Load the nth game from open file f */
10192 int
10193 LoadGame(f, gameNumber, title, useList)
10194      FILE *f;
10195      int gameNumber;
10196      char *title;
10197      int useList;
10198 {
10199     ChessMove cm;
10200     char buf[MSG_SIZ];
10201     int gn = gameNumber;
10202     ListGame *lg = NULL;
10203     int numPGNTags = 0;
10204     int err;
10205     GameMode oldGameMode;
10206     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10207
10208     if (appData.debugMode)
10209         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10210
10211     if (gameMode == Training )
10212         SetTrainingModeOff();
10213
10214     oldGameMode = gameMode;
10215     if (gameMode != BeginningOfGame) {
10216       Reset(FALSE, TRUE);
10217     }
10218
10219     gameFileFP = f;
10220     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10221         fclose(lastLoadGameFP);
10222     }
10223
10224     if (useList) {
10225         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10226
10227         if (lg) {
10228             fseek(f, lg->offset, 0);
10229             GameListHighlight(gameNumber);
10230             gn = 1;
10231         }
10232         else {
10233             DisplayError(_("Game number out of range"), 0);
10234             return FALSE;
10235         }
10236     } else {
10237         GameListDestroy();
10238         if (fseek(f, 0, 0) == -1) {
10239             if (f == lastLoadGameFP ?
10240                 gameNumber == lastLoadGameNumber + 1 :
10241                 gameNumber == 1) {
10242                 gn = 1;
10243             } else {
10244                 DisplayError(_("Can't seek on game file"), 0);
10245                 return FALSE;
10246             }
10247         }
10248     }
10249     lastLoadGameFP = f;
10250     lastLoadGameNumber = gameNumber;
10251     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10252     lastLoadGameUseList = useList;
10253
10254     yynewfile(f);
10255
10256     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10257       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10258                 lg->gameInfo.black);
10259             DisplayTitle(buf);
10260     } else if (*title != NULLCHAR) {
10261         if (gameNumber > 1) {
10262           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10263             DisplayTitle(buf);
10264         } else {
10265             DisplayTitle(title);
10266         }
10267     }
10268
10269     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10270         gameMode = PlayFromGameFile;
10271         ModeHighlight();
10272     }
10273
10274     currentMove = forwardMostMove = backwardMostMove = 0;
10275     CopyBoard(boards[0], initialPosition);
10276     StopClocks();
10277
10278     /*
10279      * Skip the first gn-1 games in the file.
10280      * Also skip over anything that precedes an identifiable
10281      * start of game marker, to avoid being confused by
10282      * garbage at the start of the file.  Currently
10283      * recognized start of game markers are the move number "1",
10284      * the pattern "gnuchess .* game", the pattern
10285      * "^[#;%] [^ ]* game file", and a PGN tag block.
10286      * A game that starts with one of the latter two patterns
10287      * will also have a move number 1, possibly
10288      * following a position diagram.
10289      * 5-4-02: Let's try being more lenient and allowing a game to
10290      * start with an unnumbered move.  Does that break anything?
10291      */
10292     cm = lastLoadGameStart = EndOfFile;
10293     while (gn > 0) {
10294         yyboardindex = forwardMostMove;
10295         cm = (ChessMove) Myylex();
10296         switch (cm) {
10297           case EndOfFile:
10298             if (cmailMsgLoaded) {
10299                 nCmailGames = CMAIL_MAX_GAMES - gn;
10300             } else {
10301                 Reset(TRUE, TRUE);
10302                 DisplayError(_("Game not found in file"), 0);
10303             }
10304             return FALSE;
10305
10306           case GNUChessGame:
10307           case XBoardGame:
10308             gn--;
10309             lastLoadGameStart = cm;
10310             break;
10311
10312           case MoveNumberOne:
10313             switch (lastLoadGameStart) {
10314               case GNUChessGame:
10315               case XBoardGame:
10316               case PGNTag:
10317                 break;
10318               case MoveNumberOne:
10319               case EndOfFile:
10320                 gn--;           /* count this game */
10321                 lastLoadGameStart = cm;
10322                 break;
10323               default:
10324                 /* impossible */
10325                 break;
10326             }
10327             break;
10328
10329           case PGNTag:
10330             switch (lastLoadGameStart) {
10331               case GNUChessGame:
10332               case PGNTag:
10333               case MoveNumberOne:
10334               case EndOfFile:
10335                 gn--;           /* count this game */
10336                 lastLoadGameStart = cm;
10337                 break;
10338               case XBoardGame:
10339                 lastLoadGameStart = cm; /* game counted already */
10340                 break;
10341               default:
10342                 /* impossible */
10343                 break;
10344             }
10345             if (gn > 0) {
10346                 do {
10347                     yyboardindex = forwardMostMove;
10348                     cm = (ChessMove) Myylex();
10349                 } while (cm == PGNTag || cm == Comment);
10350             }
10351             break;
10352
10353           case WhiteWins:
10354           case BlackWins:
10355           case GameIsDrawn:
10356             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10357                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10358                     != CMAIL_OLD_RESULT) {
10359                     nCmailResults ++ ;
10360                     cmailResult[  CMAIL_MAX_GAMES
10361                                 - gn - 1] = CMAIL_OLD_RESULT;
10362                 }
10363             }
10364             break;
10365
10366           case NormalMove:
10367             /* Only a NormalMove can be at the start of a game
10368              * without a position diagram. */
10369             if (lastLoadGameStart == EndOfFile ) {
10370               gn--;
10371               lastLoadGameStart = MoveNumberOne;
10372             }
10373             break;
10374
10375           default:
10376             break;
10377         }
10378     }
10379
10380     if (appData.debugMode)
10381       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10382
10383     if (cm == XBoardGame) {
10384         /* Skip any header junk before position diagram and/or move 1 */
10385         for (;;) {
10386             yyboardindex = forwardMostMove;
10387             cm = (ChessMove) Myylex();
10388
10389             if (cm == EndOfFile ||
10390                 cm == GNUChessGame || cm == XBoardGame) {
10391                 /* Empty game; pretend end-of-file and handle later */
10392                 cm = EndOfFile;
10393                 break;
10394             }
10395
10396             if (cm == MoveNumberOne || cm == PositionDiagram ||
10397                 cm == PGNTag || cm == Comment)
10398               break;
10399         }
10400     } else if (cm == GNUChessGame) {
10401         if (gameInfo.event != NULL) {
10402             free(gameInfo.event);
10403         }
10404         gameInfo.event = StrSave(yy_text);
10405     }
10406
10407     startedFromSetupPosition = FALSE;
10408     while (cm == PGNTag) {
10409         if (appData.debugMode)
10410           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10411         err = ParsePGNTag(yy_text, &gameInfo);
10412         if (!err) numPGNTags++;
10413
10414         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10415         if(gameInfo.variant != oldVariant) {
10416             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10417             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10418             InitPosition(TRUE);
10419             oldVariant = gameInfo.variant;
10420             if (appData.debugMode)
10421               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10422         }
10423
10424
10425         if (gameInfo.fen != NULL) {
10426           Board initial_position;
10427           startedFromSetupPosition = TRUE;
10428           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10429             Reset(TRUE, TRUE);
10430             DisplayError(_("Bad FEN position in file"), 0);
10431             return FALSE;
10432           }
10433           CopyBoard(boards[0], initial_position);
10434           if (blackPlaysFirst) {
10435             currentMove = forwardMostMove = backwardMostMove = 1;
10436             CopyBoard(boards[1], initial_position);
10437             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10438             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10439             timeRemaining[0][1] = whiteTimeRemaining;
10440             timeRemaining[1][1] = blackTimeRemaining;
10441             if (commentList[0] != NULL) {
10442               commentList[1] = commentList[0];
10443               commentList[0] = NULL;
10444             }
10445           } else {
10446             currentMove = forwardMostMove = backwardMostMove = 0;
10447           }
10448           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10449           {   int i;
10450               initialRulePlies = FENrulePlies;
10451               for( i=0; i< nrCastlingRights; i++ )
10452                   initialRights[i] = initial_position[CASTLING][i];
10453           }
10454           yyboardindex = forwardMostMove;
10455           free(gameInfo.fen);
10456           gameInfo.fen = NULL;
10457         }
10458
10459         yyboardindex = forwardMostMove;
10460         cm = (ChessMove) Myylex();
10461
10462         /* Handle comments interspersed among the tags */
10463         while (cm == Comment) {
10464             char *p;
10465             if (appData.debugMode)
10466               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10467             p = yy_text;
10468             AppendComment(currentMove, p, FALSE);
10469             yyboardindex = forwardMostMove;
10470             cm = (ChessMove) Myylex();
10471         }
10472     }
10473
10474     /* don't rely on existence of Event tag since if game was
10475      * pasted from clipboard the Event tag may not exist
10476      */
10477     if (numPGNTags > 0){
10478         char *tags;
10479         if (gameInfo.variant == VariantNormal) {
10480           VariantClass v = StringToVariant(gameInfo.event);
10481           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10482           if(v < VariantShogi) gameInfo.variant = v;
10483         }
10484         if (!matchMode) {
10485           if( appData.autoDisplayTags ) {
10486             tags = PGNTags(&gameInfo);
10487             TagsPopUp(tags, CmailMsg());
10488             free(tags);
10489           }
10490         }
10491     } else {
10492         /* Make something up, but don't display it now */
10493         SetGameInfo();
10494         TagsPopDown();
10495     }
10496
10497     if (cm == PositionDiagram) {
10498         int i, j;
10499         char *p;
10500         Board initial_position;
10501
10502         if (appData.debugMode)
10503           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10504
10505         if (!startedFromSetupPosition) {
10506             p = yy_text;
10507             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10508               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10509                 switch (*p) {
10510                   case '{':
10511                   case '[':
10512                   case '-':
10513                   case ' ':
10514                   case '\t':
10515                   case '\n':
10516                   case '\r':
10517                     break;
10518                   default:
10519                     initial_position[i][j++] = CharToPiece(*p);
10520                     break;
10521                 }
10522             while (*p == ' ' || *p == '\t' ||
10523                    *p == '\n' || *p == '\r') p++;
10524
10525             if (strncmp(p, "black", strlen("black"))==0)
10526               blackPlaysFirst = TRUE;
10527             else
10528               blackPlaysFirst = FALSE;
10529             startedFromSetupPosition = TRUE;
10530
10531             CopyBoard(boards[0], initial_position);
10532             if (blackPlaysFirst) {
10533                 currentMove = forwardMostMove = backwardMostMove = 1;
10534                 CopyBoard(boards[1], initial_position);
10535                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10536                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10537                 timeRemaining[0][1] = whiteTimeRemaining;
10538                 timeRemaining[1][1] = blackTimeRemaining;
10539                 if (commentList[0] != NULL) {
10540                     commentList[1] = commentList[0];
10541                     commentList[0] = NULL;
10542                 }
10543             } else {
10544                 currentMove = forwardMostMove = backwardMostMove = 0;
10545             }
10546         }
10547         yyboardindex = forwardMostMove;
10548         cm = (ChessMove) Myylex();
10549     }
10550
10551     if (first.pr == NoProc) {
10552         StartChessProgram(&first);
10553     }
10554     InitChessProgram(&first, FALSE);
10555     SendToProgram("force\n", &first);
10556     if (startedFromSetupPosition) {
10557         SendBoard(&first, forwardMostMove);
10558     if (appData.debugMode) {
10559         fprintf(debugFP, "Load Game\n");
10560     }
10561         DisplayBothClocks();
10562     }
10563
10564     /* [HGM] server: flag to write setup moves in broadcast file as one */
10565     loadFlag = appData.suppressLoadMoves;
10566
10567     while (cm == Comment) {
10568         char *p;
10569         if (appData.debugMode)
10570           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10571         p = yy_text;
10572         AppendComment(currentMove, p, FALSE);
10573         yyboardindex = forwardMostMove;
10574         cm = (ChessMove) Myylex();
10575     }
10576
10577     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10578         cm == WhiteWins || cm == BlackWins ||
10579         cm == GameIsDrawn || cm == GameUnfinished) {
10580         DisplayMessage("", _("No moves in game"));
10581         if (cmailMsgLoaded) {
10582             if (appData.debugMode)
10583               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10584             ClearHighlights();
10585             flipView = FALSE;
10586         }
10587         DrawPosition(FALSE, boards[currentMove]);
10588         DisplayBothClocks();
10589         gameMode = EditGame;
10590         ModeHighlight();
10591         gameFileFP = NULL;
10592         cmailOldMove = 0;
10593         return TRUE;
10594     }
10595
10596     // [HGM] PV info: routine tests if comment empty
10597     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10598         DisplayComment(currentMove - 1, commentList[currentMove]);
10599     }
10600     if (!matchMode && appData.timeDelay != 0)
10601       DrawPosition(FALSE, boards[currentMove]);
10602
10603     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10604       programStats.ok_to_send = 1;
10605     }
10606
10607     /* if the first token after the PGN tags is a move
10608      * and not move number 1, retrieve it from the parser
10609      */
10610     if (cm != MoveNumberOne)
10611         LoadGameOneMove(cm);
10612
10613     /* load the remaining moves from the file */
10614     while (LoadGameOneMove(EndOfFile)) {
10615       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10616       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10617     }
10618
10619     /* rewind to the start of the game */
10620     currentMove = backwardMostMove;
10621
10622     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10623
10624     if (oldGameMode == AnalyzeFile ||
10625         oldGameMode == AnalyzeMode) {
10626       AnalyzeFileEvent();
10627     }
10628
10629     if (matchMode || appData.timeDelay == 0) {
10630       ToEndEvent();
10631       gameMode = EditGame;
10632       ModeHighlight();
10633     } else if (appData.timeDelay > 0) {
10634       AutoPlayGameLoop();
10635     }
10636
10637     if (appData.debugMode)
10638         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10639
10640     loadFlag = 0; /* [HGM] true game starts */
10641     return TRUE;
10642 }
10643
10644 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10645 int
10646 ReloadPosition(offset)
10647      int offset;
10648 {
10649     int positionNumber = lastLoadPositionNumber + offset;
10650     if (lastLoadPositionFP == NULL) {
10651         DisplayError(_("No position has been loaded yet"), 0);
10652         return FALSE;
10653     }
10654     if (positionNumber <= 0) {
10655         DisplayError(_("Can't back up any further"), 0);
10656         return FALSE;
10657     }
10658     return LoadPosition(lastLoadPositionFP, positionNumber,
10659                         lastLoadPositionTitle);
10660 }
10661
10662 /* Load the nth position from the given file */
10663 int
10664 LoadPositionFromFile(filename, n, title)
10665      char *filename;
10666      int n;
10667      char *title;
10668 {
10669     FILE *f;
10670     char buf[MSG_SIZ];
10671
10672     if (strcmp(filename, "-") == 0) {
10673         return LoadPosition(stdin, n, "stdin");
10674     } else {
10675         f = fopen(filename, "rb");
10676         if (f == NULL) {
10677             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10678             DisplayError(buf, errno);
10679             return FALSE;
10680         } else {
10681             return LoadPosition(f, n, title);
10682         }
10683     }
10684 }
10685
10686 /* Load the nth position from the given open file, and close it */
10687 int
10688 LoadPosition(f, positionNumber, title)
10689      FILE *f;
10690      int positionNumber;
10691      char *title;
10692 {
10693     char *p, line[MSG_SIZ];
10694     Board initial_position;
10695     int i, j, fenMode, pn;
10696
10697     if (gameMode == Training )
10698         SetTrainingModeOff();
10699
10700     if (gameMode != BeginningOfGame) {
10701         Reset(FALSE, TRUE);
10702     }
10703     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10704         fclose(lastLoadPositionFP);
10705     }
10706     if (positionNumber == 0) positionNumber = 1;
10707     lastLoadPositionFP = f;
10708     lastLoadPositionNumber = positionNumber;
10709     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10710     if (first.pr == NoProc) {
10711       StartChessProgram(&first);
10712       InitChessProgram(&first, FALSE);
10713     }
10714     pn = positionNumber;
10715     if (positionNumber < 0) {
10716         /* Negative position number means to seek to that byte offset */
10717         if (fseek(f, -positionNumber, 0) == -1) {
10718             DisplayError(_("Can't seek on position file"), 0);
10719             return FALSE;
10720         };
10721         pn = 1;
10722     } else {
10723         if (fseek(f, 0, 0) == -1) {
10724             if (f == lastLoadPositionFP ?
10725                 positionNumber == lastLoadPositionNumber + 1 :
10726                 positionNumber == 1) {
10727                 pn = 1;
10728             } else {
10729                 DisplayError(_("Can't seek on position file"), 0);
10730                 return FALSE;
10731             }
10732         }
10733     }
10734     /* See if this file is FEN or old-style xboard */
10735     if (fgets(line, MSG_SIZ, f) == NULL) {
10736         DisplayError(_("Position not found in file"), 0);
10737         return FALSE;
10738     }
10739     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10740     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10741
10742     if (pn >= 2) {
10743         if (fenMode || line[0] == '#') pn--;
10744         while (pn > 0) {
10745             /* skip positions before number pn */
10746             if (fgets(line, MSG_SIZ, f) == NULL) {
10747                 Reset(TRUE, TRUE);
10748                 DisplayError(_("Position not found in file"), 0);
10749                 return FALSE;
10750             }
10751             if (fenMode || line[0] == '#') pn--;
10752         }
10753     }
10754
10755     if (fenMode) {
10756         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10757             DisplayError(_("Bad FEN position in file"), 0);
10758             return FALSE;
10759         }
10760     } else {
10761         (void) fgets(line, MSG_SIZ, f);
10762         (void) fgets(line, MSG_SIZ, f);
10763
10764         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10765             (void) fgets(line, MSG_SIZ, f);
10766             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10767                 if (*p == ' ')
10768                   continue;
10769                 initial_position[i][j++] = CharToPiece(*p);
10770             }
10771         }
10772
10773         blackPlaysFirst = FALSE;
10774         if (!feof(f)) {
10775             (void) fgets(line, MSG_SIZ, f);
10776             if (strncmp(line, "black", strlen("black"))==0)
10777               blackPlaysFirst = TRUE;
10778         }
10779     }
10780     startedFromSetupPosition = TRUE;
10781
10782     SendToProgram("force\n", &first);
10783     CopyBoard(boards[0], initial_position);
10784     if (blackPlaysFirst) {
10785         currentMove = forwardMostMove = backwardMostMove = 1;
10786         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10787         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10788         CopyBoard(boards[1], initial_position);
10789         DisplayMessage("", _("Black to play"));
10790     } else {
10791         currentMove = forwardMostMove = backwardMostMove = 0;
10792         DisplayMessage("", _("White to play"));
10793     }
10794     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10795     SendBoard(&first, forwardMostMove);
10796     if (appData.debugMode) {
10797 int i, j;
10798   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10799   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10800         fprintf(debugFP, "Load Position\n");
10801     }
10802
10803     if (positionNumber > 1) {
10804       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10805         DisplayTitle(line);
10806     } else {
10807         DisplayTitle(title);
10808     }
10809     gameMode = EditGame;
10810     ModeHighlight();
10811     ResetClocks();
10812     timeRemaining[0][1] = whiteTimeRemaining;
10813     timeRemaining[1][1] = blackTimeRemaining;
10814     DrawPosition(FALSE, boards[currentMove]);
10815
10816     return TRUE;
10817 }
10818
10819
10820 void
10821 CopyPlayerNameIntoFileName(dest, src)
10822      char **dest, *src;
10823 {
10824     while (*src != NULLCHAR && *src != ',') {
10825         if (*src == ' ') {
10826             *(*dest)++ = '_';
10827             src++;
10828         } else {
10829             *(*dest)++ = *src++;
10830         }
10831     }
10832 }
10833
10834 char *DefaultFileName(ext)
10835      char *ext;
10836 {
10837     static char def[MSG_SIZ];
10838     char *p;
10839
10840     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10841         p = def;
10842         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10843         *p++ = '-';
10844         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10845         *p++ = '.';
10846         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10847     } else {
10848         def[0] = NULLCHAR;
10849     }
10850     return def;
10851 }
10852
10853 /* Save the current game to the given file */
10854 int
10855 SaveGameToFile(filename, append)
10856      char *filename;
10857      int append;
10858 {
10859     FILE *f;
10860     char buf[MSG_SIZ];
10861
10862     if (strcmp(filename, "-") == 0) {
10863         return SaveGame(stdout, 0, NULL);
10864     } else {
10865         f = fopen(filename, append ? "a" : "w");
10866         if (f == NULL) {
10867             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10868             DisplayError(buf, errno);
10869             return FALSE;
10870         } else {
10871             return SaveGame(f, 0, NULL);
10872         }
10873     }
10874 }
10875
10876 char *
10877 SavePart(str)
10878      char *str;
10879 {
10880     static char buf[MSG_SIZ];
10881     char *p;
10882
10883     p = strchr(str, ' ');
10884     if (p == NULL) return str;
10885     strncpy(buf, str, p - str);
10886     buf[p - str] = NULLCHAR;
10887     return buf;
10888 }
10889
10890 #define PGN_MAX_LINE 75
10891
10892 #define PGN_SIDE_WHITE  0
10893 #define PGN_SIDE_BLACK  1
10894
10895 /* [AS] */
10896 static int FindFirstMoveOutOfBook( int side )
10897 {
10898     int result = -1;
10899
10900     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10901         int index = backwardMostMove;
10902         int has_book_hit = 0;
10903
10904         if( (index % 2) != side ) {
10905             index++;
10906         }
10907
10908         while( index < forwardMostMove ) {
10909             /* Check to see if engine is in book */
10910             int depth = pvInfoList[index].depth;
10911             int score = pvInfoList[index].score;
10912             int in_book = 0;
10913
10914             if( depth <= 2 ) {
10915                 in_book = 1;
10916             }
10917             else if( score == 0 && depth == 63 ) {
10918                 in_book = 1; /* Zappa */
10919             }
10920             else if( score == 2 && depth == 99 ) {
10921                 in_book = 1; /* Abrok */
10922             }
10923
10924             has_book_hit += in_book;
10925
10926             if( ! in_book ) {
10927                 result = index;
10928
10929                 break;
10930             }
10931
10932             index += 2;
10933         }
10934     }
10935
10936     return result;
10937 }
10938
10939 /* [AS] */
10940 void GetOutOfBookInfo( char * buf )
10941 {
10942     int oob[2];
10943     int i;
10944     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10945
10946     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10947     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10948
10949     *buf = '\0';
10950
10951     if( oob[0] >= 0 || oob[1] >= 0 ) {
10952         for( i=0; i<2; i++ ) {
10953             int idx = oob[i];
10954
10955             if( idx >= 0 ) {
10956                 if( i > 0 && oob[0] >= 0 ) {
10957                     strcat( buf, "   " );
10958                 }
10959
10960                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10961                 sprintf( buf+strlen(buf), "%s%.2f",
10962                     pvInfoList[idx].score >= 0 ? "+" : "",
10963                     pvInfoList[idx].score / 100.0 );
10964             }
10965         }
10966     }
10967 }
10968
10969 /* Save game in PGN style and close the file */
10970 int
10971 SaveGamePGN(f)
10972      FILE *f;
10973 {
10974     int i, offset, linelen, newblock;
10975     time_t tm;
10976 //    char *movetext;
10977     char numtext[32];
10978     int movelen, numlen, blank;
10979     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10980
10981     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10982
10983     tm = time((time_t *) NULL);
10984
10985     PrintPGNTags(f, &gameInfo);
10986
10987     if (backwardMostMove > 0 || startedFromSetupPosition) {
10988         char *fen = PositionToFEN(backwardMostMove, NULL);
10989         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10990         fprintf(f, "\n{--------------\n");
10991         PrintPosition(f, backwardMostMove);
10992         fprintf(f, "--------------}\n");
10993         free(fen);
10994     }
10995     else {
10996         /* [AS] Out of book annotation */
10997         if( appData.saveOutOfBookInfo ) {
10998             char buf[64];
10999
11000             GetOutOfBookInfo( buf );
11001
11002             if( buf[0] != '\0' ) {
11003                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11004             }
11005         }
11006
11007         fprintf(f, "\n");
11008     }
11009
11010     i = backwardMostMove;
11011     linelen = 0;
11012     newblock = TRUE;
11013
11014     while (i < forwardMostMove) {
11015         /* Print comments preceding this move */
11016         if (commentList[i] != NULL) {
11017             if (linelen > 0) fprintf(f, "\n");
11018             fprintf(f, "%s", commentList[i]);
11019             linelen = 0;
11020             newblock = TRUE;
11021         }
11022
11023         /* Format move number */
11024         if ((i % 2) == 0)
11025           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11026         else
11027           if (newblock)
11028             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11029           else
11030             numtext[0] = NULLCHAR;
11031
11032         numlen = strlen(numtext);
11033         newblock = FALSE;
11034
11035         /* Print move number */
11036         blank = linelen > 0 && numlen > 0;
11037         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11038             fprintf(f, "\n");
11039             linelen = 0;
11040             blank = 0;
11041         }
11042         if (blank) {
11043             fprintf(f, " ");
11044             linelen++;
11045         }
11046         fprintf(f, "%s", numtext);
11047         linelen += numlen;
11048
11049         /* Get move */
11050         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11051         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11052
11053         /* Print move */
11054         blank = linelen > 0 && movelen > 0;
11055         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11056             fprintf(f, "\n");
11057             linelen = 0;
11058             blank = 0;
11059         }
11060         if (blank) {
11061             fprintf(f, " ");
11062             linelen++;
11063         }
11064         fprintf(f, "%s", move_buffer);
11065         linelen += movelen;
11066
11067         /* [AS] Add PV info if present */
11068         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11069             /* [HGM] add time */
11070             char buf[MSG_SIZ]; int seconds;
11071
11072             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11073
11074             if( seconds <= 0)
11075               buf[0] = 0;
11076             else
11077               if( seconds < 30 )
11078                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11079               else
11080                 {
11081                   seconds = (seconds + 4)/10; // round to full seconds
11082                   if( seconds < 60 )
11083                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11084                   else
11085                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11086                 }
11087
11088             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11089                       pvInfoList[i].score >= 0 ? "+" : "",
11090                       pvInfoList[i].score / 100.0,
11091                       pvInfoList[i].depth,
11092                       buf );
11093
11094             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11095
11096             /* Print score/depth */
11097             blank = linelen > 0 && movelen > 0;
11098             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11099                 fprintf(f, "\n");
11100                 linelen = 0;
11101                 blank = 0;
11102             }
11103             if (blank) {
11104                 fprintf(f, " ");
11105                 linelen++;
11106             }
11107             fprintf(f, "%s", move_buffer);
11108             linelen += movelen;
11109         }
11110
11111         i++;
11112     }
11113
11114     /* Start a new line */
11115     if (linelen > 0) fprintf(f, "\n");
11116
11117     /* Print comments after last move */
11118     if (commentList[i] != NULL) {
11119         fprintf(f, "%s\n", commentList[i]);
11120     }
11121
11122     /* Print result */
11123     if (gameInfo.resultDetails != NULL &&
11124         gameInfo.resultDetails[0] != NULLCHAR) {
11125         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11126                 PGNResult(gameInfo.result));
11127     } else {
11128         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11129     }
11130
11131     fclose(f);
11132     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11133     return TRUE;
11134 }
11135
11136 /* Save game in old style and close the file */
11137 int
11138 SaveGameOldStyle(f)
11139      FILE *f;
11140 {
11141     int i, offset;
11142     time_t tm;
11143
11144     tm = time((time_t *) NULL);
11145
11146     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11147     PrintOpponents(f);
11148
11149     if (backwardMostMove > 0 || startedFromSetupPosition) {
11150         fprintf(f, "\n[--------------\n");
11151         PrintPosition(f, backwardMostMove);
11152         fprintf(f, "--------------]\n");
11153     } else {
11154         fprintf(f, "\n");
11155     }
11156
11157     i = backwardMostMove;
11158     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11159
11160     while (i < forwardMostMove) {
11161         if (commentList[i] != NULL) {
11162             fprintf(f, "[%s]\n", commentList[i]);
11163         }
11164
11165         if ((i % 2) == 1) {
11166             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11167             i++;
11168         } else {
11169             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11170             i++;
11171             if (commentList[i] != NULL) {
11172                 fprintf(f, "\n");
11173                 continue;
11174             }
11175             if (i >= forwardMostMove) {
11176                 fprintf(f, "\n");
11177                 break;
11178             }
11179             fprintf(f, "%s\n", parseList[i]);
11180             i++;
11181         }
11182     }
11183
11184     if (commentList[i] != NULL) {
11185         fprintf(f, "[%s]\n", commentList[i]);
11186     }
11187
11188     /* This isn't really the old style, but it's close enough */
11189     if (gameInfo.resultDetails != NULL &&
11190         gameInfo.resultDetails[0] != NULLCHAR) {
11191         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11192                 gameInfo.resultDetails);
11193     } else {
11194         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11195     }
11196
11197     fclose(f);
11198     return TRUE;
11199 }
11200
11201 /* Save the current game to open file f and close the file */
11202 int
11203 SaveGame(f, dummy, dummy2)
11204      FILE *f;
11205      int dummy;
11206      char *dummy2;
11207 {
11208     if (gameMode == EditPosition) EditPositionDone(TRUE);
11209     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11210     if (appData.oldSaveStyle)
11211       return SaveGameOldStyle(f);
11212     else
11213       return SaveGamePGN(f);
11214 }
11215
11216 /* Save the current position to the given file */
11217 int
11218 SavePositionToFile(filename)
11219      char *filename;
11220 {
11221     FILE *f;
11222     char buf[MSG_SIZ];
11223
11224     if (strcmp(filename, "-") == 0) {
11225         return SavePosition(stdout, 0, NULL);
11226     } else {
11227         f = fopen(filename, "a");
11228         if (f == NULL) {
11229             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11230             DisplayError(buf, errno);
11231             return FALSE;
11232         } else {
11233             SavePosition(f, 0, NULL);
11234             return TRUE;
11235         }
11236     }
11237 }
11238
11239 /* Save the current position to the given open file and close the file */
11240 int
11241 SavePosition(f, dummy, dummy2)
11242      FILE *f;
11243      int dummy;
11244      char *dummy2;
11245 {
11246     time_t tm;
11247     char *fen;
11248
11249     if (gameMode == EditPosition) EditPositionDone(TRUE);
11250     if (appData.oldSaveStyle) {
11251         tm = time((time_t *) NULL);
11252
11253         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11254         PrintOpponents(f);
11255         fprintf(f, "[--------------\n");
11256         PrintPosition(f, currentMove);
11257         fprintf(f, "--------------]\n");
11258     } else {
11259         fen = PositionToFEN(currentMove, NULL);
11260         fprintf(f, "%s\n", fen);
11261         free(fen);
11262     }
11263     fclose(f);
11264     return TRUE;
11265 }
11266
11267 void
11268 ReloadCmailMsgEvent(unregister)
11269      int unregister;
11270 {
11271 #if !WIN32
11272     static char *inFilename = NULL;
11273     static char *outFilename;
11274     int i;
11275     struct stat inbuf, outbuf;
11276     int status;
11277
11278     /* Any registered moves are unregistered if unregister is set, */
11279     /* i.e. invoked by the signal handler */
11280     if (unregister) {
11281         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11282             cmailMoveRegistered[i] = FALSE;
11283             if (cmailCommentList[i] != NULL) {
11284                 free(cmailCommentList[i]);
11285                 cmailCommentList[i] = NULL;
11286             }
11287         }
11288         nCmailMovesRegistered = 0;
11289     }
11290
11291     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11292         cmailResult[i] = CMAIL_NOT_RESULT;
11293     }
11294     nCmailResults = 0;
11295
11296     if (inFilename == NULL) {
11297         /* Because the filenames are static they only get malloced once  */
11298         /* and they never get freed                                      */
11299         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11300         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11301
11302         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11303         sprintf(outFilename, "%s.out", appData.cmailGameName);
11304     }
11305
11306     status = stat(outFilename, &outbuf);
11307     if (status < 0) {
11308         cmailMailedMove = FALSE;
11309     } else {
11310         status = stat(inFilename, &inbuf);
11311         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11312     }
11313
11314     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11315        counts the games, notes how each one terminated, etc.
11316
11317        It would be nice to remove this kludge and instead gather all
11318        the information while building the game list.  (And to keep it
11319        in the game list nodes instead of having a bunch of fixed-size
11320        parallel arrays.)  Note this will require getting each game's
11321        termination from the PGN tags, as the game list builder does
11322        not process the game moves.  --mann
11323        */
11324     cmailMsgLoaded = TRUE;
11325     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11326
11327     /* Load first game in the file or popup game menu */
11328     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11329
11330 #endif /* !WIN32 */
11331     return;
11332 }
11333
11334 int
11335 RegisterMove()
11336 {
11337     FILE *f;
11338     char string[MSG_SIZ];
11339
11340     if (   cmailMailedMove
11341         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11342         return TRUE;            /* Allow free viewing  */
11343     }
11344
11345     /* Unregister move to ensure that we don't leave RegisterMove        */
11346     /* with the move registered when the conditions for registering no   */
11347     /* longer hold                                                       */
11348     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11349         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11350         nCmailMovesRegistered --;
11351
11352         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11353           {
11354               free(cmailCommentList[lastLoadGameNumber - 1]);
11355               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11356           }
11357     }
11358
11359     if (cmailOldMove == -1) {
11360         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11361         return FALSE;
11362     }
11363
11364     if (currentMove > cmailOldMove + 1) {
11365         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11366         return FALSE;
11367     }
11368
11369     if (currentMove < cmailOldMove) {
11370         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11371         return FALSE;
11372     }
11373
11374     if (forwardMostMove > currentMove) {
11375         /* Silently truncate extra moves */
11376         TruncateGame();
11377     }
11378
11379     if (   (currentMove == cmailOldMove + 1)
11380         || (   (currentMove == cmailOldMove)
11381             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11382                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11383         if (gameInfo.result != GameUnfinished) {
11384             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11385         }
11386
11387         if (commentList[currentMove] != NULL) {
11388             cmailCommentList[lastLoadGameNumber - 1]
11389               = StrSave(commentList[currentMove]);
11390         }
11391         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11392
11393         if (appData.debugMode)
11394           fprintf(debugFP, "Saving %s for game %d\n",
11395                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11396
11397         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11398
11399         f = fopen(string, "w");
11400         if (appData.oldSaveStyle) {
11401             SaveGameOldStyle(f); /* also closes the file */
11402
11403             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11404             f = fopen(string, "w");
11405             SavePosition(f, 0, NULL); /* also closes the file */
11406         } else {
11407             fprintf(f, "{--------------\n");
11408             PrintPosition(f, currentMove);
11409             fprintf(f, "--------------}\n\n");
11410
11411             SaveGame(f, 0, NULL); /* also closes the file*/
11412         }
11413
11414         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11415         nCmailMovesRegistered ++;
11416     } else if (nCmailGames == 1) {
11417         DisplayError(_("You have not made a move yet"), 0);
11418         return FALSE;
11419     }
11420
11421     return TRUE;
11422 }
11423
11424 void
11425 MailMoveEvent()
11426 {
11427 #if !WIN32
11428     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11429     FILE *commandOutput;
11430     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11431     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11432     int nBuffers;
11433     int i;
11434     int archived;
11435     char *arcDir;
11436
11437     if (! cmailMsgLoaded) {
11438         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11439         return;
11440     }
11441
11442     if (nCmailGames == nCmailResults) {
11443         DisplayError(_("No unfinished games"), 0);
11444         return;
11445     }
11446
11447 #if CMAIL_PROHIBIT_REMAIL
11448     if (cmailMailedMove) {
11449       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);
11450         DisplayError(msg, 0);
11451         return;
11452     }
11453 #endif
11454
11455     if (! (cmailMailedMove || RegisterMove())) return;
11456
11457     if (   cmailMailedMove
11458         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11459       snprintf(string, MSG_SIZ, partCommandString,
11460                appData.debugMode ? " -v" : "", appData.cmailGameName);
11461         commandOutput = popen(string, "r");
11462
11463         if (commandOutput == NULL) {
11464             DisplayError(_("Failed to invoke cmail"), 0);
11465         } else {
11466             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11467                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11468             }
11469             if (nBuffers > 1) {
11470                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11471                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11472                 nBytes = MSG_SIZ - 1;
11473             } else {
11474                 (void) memcpy(msg, buffer, nBytes);
11475             }
11476             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11477
11478             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11479                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11480
11481                 archived = TRUE;
11482                 for (i = 0; i < nCmailGames; i ++) {
11483                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11484                         archived = FALSE;
11485                     }
11486                 }
11487                 if (   archived
11488                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11489                         != NULL)) {
11490                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11491                            arcDir,
11492                            appData.cmailGameName,
11493                            gameInfo.date);
11494                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11495                     cmailMsgLoaded = FALSE;
11496                 }
11497             }
11498
11499             DisplayInformation(msg);
11500             pclose(commandOutput);
11501         }
11502     } else {
11503         if ((*cmailMsg) != '\0') {
11504             DisplayInformation(cmailMsg);
11505         }
11506     }
11507
11508     return;
11509 #endif /* !WIN32 */
11510 }
11511
11512 char *
11513 CmailMsg()
11514 {
11515 #if WIN32
11516     return NULL;
11517 #else
11518     int  prependComma = 0;
11519     char number[5];
11520     char string[MSG_SIZ];       /* Space for game-list */
11521     int  i;
11522
11523     if (!cmailMsgLoaded) return "";
11524
11525     if (cmailMailedMove) {
11526       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11527     } else {
11528         /* Create a list of games left */
11529       snprintf(string, MSG_SIZ, "[");
11530         for (i = 0; i < nCmailGames; i ++) {
11531             if (! (   cmailMoveRegistered[i]
11532                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11533                 if (prependComma) {
11534                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11535                 } else {
11536                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11537                     prependComma = 1;
11538                 }
11539
11540                 strcat(string, number);
11541             }
11542         }
11543         strcat(string, "]");
11544
11545         if (nCmailMovesRegistered + nCmailResults == 0) {
11546             switch (nCmailGames) {
11547               case 1:
11548                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11549                 break;
11550
11551               case 2:
11552                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11553                 break;
11554
11555               default:
11556                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11557                          nCmailGames);
11558                 break;
11559             }
11560         } else {
11561             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11562               case 1:
11563                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11564                          string);
11565                 break;
11566
11567               case 0:
11568                 if (nCmailResults == nCmailGames) {
11569                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11570                 } else {
11571                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11572                 }
11573                 break;
11574
11575               default:
11576                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11577                          string);
11578             }
11579         }
11580     }
11581     return cmailMsg;
11582 #endif /* WIN32 */
11583 }
11584
11585 void
11586 ResetGameEvent()
11587 {
11588     if (gameMode == Training)
11589       SetTrainingModeOff();
11590
11591     Reset(TRUE, TRUE);
11592     cmailMsgLoaded = FALSE;
11593     if (appData.icsActive) {
11594       SendToICS(ics_prefix);
11595       SendToICS("refresh\n");
11596     }
11597 }
11598
11599 void
11600 ExitEvent(status)
11601      int status;
11602 {
11603     exiting++;
11604     if (exiting > 2) {
11605       /* Give up on clean exit */
11606       exit(status);
11607     }
11608     if (exiting > 1) {
11609       /* Keep trying for clean exit */
11610       return;
11611     }
11612
11613     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11614
11615     if (telnetISR != NULL) {
11616       RemoveInputSource(telnetISR);
11617     }
11618     if (icsPR != NoProc) {
11619       DestroyChildProcess(icsPR, TRUE);
11620     }
11621
11622     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11623     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11624
11625     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11626     /* make sure this other one finishes before killing it!                  */
11627     if(endingGame) { int count = 0;
11628         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11629         while(endingGame && count++ < 10) DoSleep(1);
11630         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11631     }
11632
11633     /* Kill off chess programs */
11634     if (first.pr != NoProc) {
11635         ExitAnalyzeMode();
11636
11637         DoSleep( appData.delayBeforeQuit );
11638         SendToProgram("quit\n", &first);
11639         DoSleep( appData.delayAfterQuit );
11640         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11641     }
11642     if (second.pr != NoProc) {
11643         DoSleep( appData.delayBeforeQuit );
11644         SendToProgram("quit\n", &second);
11645         DoSleep( appData.delayAfterQuit );
11646         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11647     }
11648     if (first.isr != NULL) {
11649         RemoveInputSource(first.isr);
11650     }
11651     if (second.isr != NULL) {
11652         RemoveInputSource(second.isr);
11653     }
11654
11655     ShutDownFrontEnd();
11656     exit(status);
11657 }
11658
11659 void
11660 PauseEvent()
11661 {
11662     if (appData.debugMode)
11663         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11664     if (pausing) {
11665         pausing = FALSE;
11666         ModeHighlight();
11667         if (gameMode == MachinePlaysWhite ||
11668             gameMode == MachinePlaysBlack) {
11669             StartClocks();
11670         } else {
11671             DisplayBothClocks();
11672         }
11673         if (gameMode == PlayFromGameFile) {
11674             if (appData.timeDelay >= 0)
11675                 AutoPlayGameLoop();
11676         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11677             Reset(FALSE, TRUE);
11678             SendToICS(ics_prefix);
11679             SendToICS("refresh\n");
11680         } else if (currentMove < forwardMostMove) {
11681             ForwardInner(forwardMostMove);
11682         }
11683         pauseExamInvalid = FALSE;
11684     } else {
11685         switch (gameMode) {
11686           default:
11687             return;
11688           case IcsExamining:
11689             pauseExamForwardMostMove = forwardMostMove;
11690             pauseExamInvalid = FALSE;
11691             /* fall through */
11692           case IcsObserving:
11693           case IcsPlayingWhite:
11694           case IcsPlayingBlack:
11695             pausing = TRUE;
11696             ModeHighlight();
11697             return;
11698           case PlayFromGameFile:
11699             (void) StopLoadGameTimer();
11700             pausing = TRUE;
11701             ModeHighlight();
11702             break;
11703           case BeginningOfGame:
11704             if (appData.icsActive) return;
11705             /* else fall through */
11706           case MachinePlaysWhite:
11707           case MachinePlaysBlack:
11708           case TwoMachinesPlay:
11709             if (forwardMostMove == 0)
11710               return;           /* don't pause if no one has moved */
11711             if ((gameMode == MachinePlaysWhite &&
11712                  !WhiteOnMove(forwardMostMove)) ||
11713                 (gameMode == MachinePlaysBlack &&
11714                  WhiteOnMove(forwardMostMove))) {
11715                 StopClocks();
11716             }
11717             pausing = TRUE;
11718             ModeHighlight();
11719             break;
11720         }
11721     }
11722 }
11723
11724 void
11725 EditCommentEvent()
11726 {
11727     char title[MSG_SIZ];
11728
11729     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11730       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11731     } else {
11732       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11733                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11734                parseList[currentMove - 1]);
11735     }
11736
11737     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11738 }
11739
11740
11741 void
11742 EditTagsEvent()
11743 {
11744     char *tags = PGNTags(&gameInfo);
11745     EditTagsPopUp(tags, NULL);
11746     free(tags);
11747 }
11748
11749 void
11750 AnalyzeModeEvent()
11751 {
11752     if (appData.noChessProgram || gameMode == AnalyzeMode)
11753       return;
11754
11755     if (gameMode != AnalyzeFile) {
11756         if (!appData.icsEngineAnalyze) {
11757                EditGameEvent();
11758                if (gameMode != EditGame) return;
11759         }
11760         ResurrectChessProgram();
11761         SendToProgram("analyze\n", &first);
11762         first.analyzing = TRUE;
11763         /*first.maybeThinking = TRUE;*/
11764         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11765         EngineOutputPopUp();
11766     }
11767     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11768     pausing = FALSE;
11769     ModeHighlight();
11770     SetGameInfo();
11771
11772     StartAnalysisClock();
11773     GetTimeMark(&lastNodeCountTime);
11774     lastNodeCount = 0;
11775 }
11776
11777 void
11778 AnalyzeFileEvent()
11779 {
11780     if (appData.noChessProgram || gameMode == AnalyzeFile)
11781       return;
11782
11783     if (gameMode != AnalyzeMode) {
11784         EditGameEvent();
11785         if (gameMode != EditGame) return;
11786         ResurrectChessProgram();
11787         SendToProgram("analyze\n", &first);
11788         first.analyzing = TRUE;
11789         /*first.maybeThinking = TRUE;*/
11790         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11791         EngineOutputPopUp();
11792     }
11793     gameMode = AnalyzeFile;
11794     pausing = FALSE;
11795     ModeHighlight();
11796     SetGameInfo();
11797
11798     StartAnalysisClock();
11799     GetTimeMark(&lastNodeCountTime);
11800     lastNodeCount = 0;
11801 }
11802
11803 void
11804 MachineWhiteEvent()
11805 {
11806     char buf[MSG_SIZ];
11807     char *bookHit = NULL;
11808
11809     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11810       return;
11811
11812
11813     if (gameMode == PlayFromGameFile ||
11814         gameMode == TwoMachinesPlay  ||
11815         gameMode == Training         ||
11816         gameMode == AnalyzeMode      ||
11817         gameMode == EndOfGame)
11818         EditGameEvent();
11819
11820     if (gameMode == EditPosition)
11821         EditPositionDone(TRUE);
11822
11823     if (!WhiteOnMove(currentMove)) {
11824         DisplayError(_("It is not White's turn"), 0);
11825         return;
11826     }
11827
11828     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11829       ExitAnalyzeMode();
11830
11831     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11832         gameMode == AnalyzeFile)
11833         TruncateGame();
11834
11835     ResurrectChessProgram();    /* in case it isn't running */
11836     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11837         gameMode = MachinePlaysWhite;
11838         ResetClocks();
11839     } else
11840     gameMode = MachinePlaysWhite;
11841     pausing = FALSE;
11842     ModeHighlight();
11843     SetGameInfo();
11844     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11845     DisplayTitle(buf);
11846     if (first.sendName) {
11847       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11848       SendToProgram(buf, &first);
11849     }
11850     if (first.sendTime) {
11851       if (first.useColors) {
11852         SendToProgram("black\n", &first); /*gnu kludge*/
11853       }
11854       SendTimeRemaining(&first, TRUE);
11855     }
11856     if (first.useColors) {
11857       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11858     }
11859     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11860     SetMachineThinkingEnables();
11861     first.maybeThinking = TRUE;
11862     StartClocks();
11863     firstMove = FALSE;
11864
11865     if (appData.autoFlipView && !flipView) {
11866       flipView = !flipView;
11867       DrawPosition(FALSE, NULL);
11868       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11869     }
11870
11871     if(bookHit) { // [HGM] book: simulate book reply
11872         static char bookMove[MSG_SIZ]; // a bit generous?
11873
11874         programStats.nodes = programStats.depth = programStats.time =
11875         programStats.score = programStats.got_only_move = 0;
11876         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11877
11878         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11879         strcat(bookMove, bookHit);
11880         HandleMachineMove(bookMove, &first);
11881     }
11882 }
11883
11884 void
11885 MachineBlackEvent()
11886 {
11887   char buf[MSG_SIZ];
11888   char *bookHit = NULL;
11889
11890     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11891         return;
11892
11893
11894     if (gameMode == PlayFromGameFile ||
11895         gameMode == TwoMachinesPlay  ||
11896         gameMode == Training         ||
11897         gameMode == AnalyzeMode      ||
11898         gameMode == EndOfGame)
11899         EditGameEvent();
11900
11901     if (gameMode == EditPosition)
11902         EditPositionDone(TRUE);
11903
11904     if (WhiteOnMove(currentMove)) {
11905         DisplayError(_("It is not Black's turn"), 0);
11906         return;
11907     }
11908
11909     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11910       ExitAnalyzeMode();
11911
11912     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11913         gameMode == AnalyzeFile)
11914         TruncateGame();
11915
11916     ResurrectChessProgram();    /* in case it isn't running */
11917     gameMode = MachinePlaysBlack;
11918     pausing = FALSE;
11919     ModeHighlight();
11920     SetGameInfo();
11921     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11922     DisplayTitle(buf);
11923     if (first.sendName) {
11924       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11925       SendToProgram(buf, &first);
11926     }
11927     if (first.sendTime) {
11928       if (first.useColors) {
11929         SendToProgram("white\n", &first); /*gnu kludge*/
11930       }
11931       SendTimeRemaining(&first, FALSE);
11932     }
11933     if (first.useColors) {
11934       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11935     }
11936     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11937     SetMachineThinkingEnables();
11938     first.maybeThinking = TRUE;
11939     StartClocks();
11940
11941     if (appData.autoFlipView && flipView) {
11942       flipView = !flipView;
11943       DrawPosition(FALSE, NULL);
11944       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11945     }
11946     if(bookHit) { // [HGM] book: simulate book reply
11947         static char bookMove[MSG_SIZ]; // a bit generous?
11948
11949         programStats.nodes = programStats.depth = programStats.time =
11950         programStats.score = programStats.got_only_move = 0;
11951         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11952
11953         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11954         strcat(bookMove, bookHit);
11955         HandleMachineMove(bookMove, &first);
11956     }
11957 }
11958
11959
11960 void
11961 DisplayTwoMachinesTitle()
11962 {
11963     char buf[MSG_SIZ];
11964     if (appData.matchGames > 0) {
11965         if (first.twoMachinesColor[0] == 'w') {
11966           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11967                    gameInfo.white, gameInfo.black,
11968                    first.matchWins, second.matchWins,
11969                    matchGame - 1 - (first.matchWins + second.matchWins));
11970         } else {
11971           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11972                    gameInfo.white, gameInfo.black,
11973                    second.matchWins, first.matchWins,
11974                    matchGame - 1 - (first.matchWins + second.matchWins));
11975         }
11976     } else {
11977       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11978     }
11979     DisplayTitle(buf);
11980 }
11981
11982 void
11983 SettingsMenuIfReady()
11984 {
11985   if (second.lastPing != second.lastPong) {
11986     DisplayMessage("", _("Waiting for second chess program"));
11987     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11988     return;
11989   }
11990   ThawUI();
11991   DisplayMessage("", "");
11992   SettingsPopUp(&second);
11993 }
11994
11995 int
11996 WaitForSecond(DelayedEventCallback retry)
11997 {
11998     if (second.pr == NULL) {
11999         StartChessProgram(&second);
12000         if (second.protocolVersion == 1) {
12001           retry();
12002         } else {
12003           /* kludge: allow timeout for initial "feature" command */
12004           FreezeUI();
12005           DisplayMessage("", _("Starting second chess program"));
12006           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12007         }
12008         return 1;
12009     }
12010     return 0;
12011 }
12012
12013 void
12014 TwoMachinesEvent P((void))
12015 {
12016     int i;
12017     char buf[MSG_SIZ];
12018     ChessProgramState *onmove;
12019     char *bookHit = NULL;
12020
12021     if (appData.noChessProgram) return;
12022
12023     switch (gameMode) {
12024       case TwoMachinesPlay:
12025         return;
12026       case MachinePlaysWhite:
12027       case MachinePlaysBlack:
12028         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12029             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12030             return;
12031         }
12032         /* fall through */
12033       case BeginningOfGame:
12034       case PlayFromGameFile:
12035       case EndOfGame:
12036         EditGameEvent();
12037         if (gameMode != EditGame) return;
12038         break;
12039       case EditPosition:
12040         EditPositionDone(TRUE);
12041         break;
12042       case AnalyzeMode:
12043       case AnalyzeFile:
12044         ExitAnalyzeMode();
12045         break;
12046       case EditGame:
12047       default:
12048         break;
12049     }
12050
12051 //    forwardMostMove = currentMove;
12052     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12053     ResurrectChessProgram();    /* in case first program isn't running */
12054
12055     if(WaitForSecond(TwoMachinesEventIfReady)) return;
12056     DisplayMessage("", "");
12057     InitChessProgram(&second, FALSE);
12058     SendToProgram("force\n", &second);
12059     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12060       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12061       return;
12062     }
12063     if (startedFromSetupPosition) {
12064         SendBoard(&second, backwardMostMove);
12065     if (appData.debugMode) {
12066         fprintf(debugFP, "Two Machines\n");
12067     }
12068     }
12069     for (i = backwardMostMove; i < forwardMostMove; i++) {
12070         SendMoveToProgram(i, &second);
12071     }
12072
12073     gameMode = TwoMachinesPlay;
12074     pausing = FALSE;
12075     ModeHighlight();
12076     SetGameInfo();
12077     DisplayTwoMachinesTitle();
12078     firstMove = TRUE;
12079     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12080         onmove = &first;
12081     } else {
12082         onmove = &second;
12083     }
12084
12085     SendToProgram(first.computerString, &first);
12086     if (first.sendName) {
12087       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12088       SendToProgram(buf, &first);
12089     }
12090     SendToProgram(second.computerString, &second);
12091     if (second.sendName) {
12092       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12093       SendToProgram(buf, &second);
12094     }
12095
12096     ResetClocks();
12097     if (!first.sendTime || !second.sendTime) {
12098         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12099         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12100     }
12101     if (onmove->sendTime) {
12102       if (onmove->useColors) {
12103         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12104       }
12105       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12106     }
12107     if (onmove->useColors) {
12108       SendToProgram(onmove->twoMachinesColor, onmove);
12109     }
12110     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12111 //    SendToProgram("go\n", onmove);
12112     onmove->maybeThinking = TRUE;
12113     SetMachineThinkingEnables();
12114
12115     StartClocks();
12116
12117     if(bookHit) { // [HGM] book: simulate book reply
12118         static char bookMove[MSG_SIZ]; // a bit generous?
12119
12120         programStats.nodes = programStats.depth = programStats.time =
12121         programStats.score = programStats.got_only_move = 0;
12122         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12123
12124         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12125         strcat(bookMove, bookHit);
12126         savedMessage = bookMove; // args for deferred call
12127         savedState = onmove;
12128         ScheduleDelayedEvent(DeferredBookMove, 1);
12129     }
12130 }
12131
12132 void
12133 TrainingEvent()
12134 {
12135     if (gameMode == Training) {
12136       SetTrainingModeOff();
12137       gameMode = PlayFromGameFile;
12138       DisplayMessage("", _("Training mode off"));
12139     } else {
12140       gameMode = Training;
12141       animateTraining = appData.animate;
12142
12143       /* make sure we are not already at the end of the game */
12144       if (currentMove < forwardMostMove) {
12145         SetTrainingModeOn();
12146         DisplayMessage("", _("Training mode on"));
12147       } else {
12148         gameMode = PlayFromGameFile;
12149         DisplayError(_("Already at end of game"), 0);
12150       }
12151     }
12152     ModeHighlight();
12153 }
12154
12155 void
12156 IcsClientEvent()
12157 {
12158     if (!appData.icsActive) return;
12159     switch (gameMode) {
12160       case IcsPlayingWhite:
12161       case IcsPlayingBlack:
12162       case IcsObserving:
12163       case IcsIdle:
12164       case BeginningOfGame:
12165       case IcsExamining:
12166         return;
12167
12168       case EditGame:
12169         break;
12170
12171       case EditPosition:
12172         EditPositionDone(TRUE);
12173         break;
12174
12175       case AnalyzeMode:
12176       case AnalyzeFile:
12177         ExitAnalyzeMode();
12178         break;
12179
12180       default:
12181         EditGameEvent();
12182         break;
12183     }
12184
12185     gameMode = IcsIdle;
12186     ModeHighlight();
12187     return;
12188 }
12189
12190
12191 void
12192 EditGameEvent()
12193 {
12194     int i;
12195
12196     switch (gameMode) {
12197       case Training:
12198         SetTrainingModeOff();
12199         break;
12200       case MachinePlaysWhite:
12201       case MachinePlaysBlack:
12202       case BeginningOfGame:
12203         SendToProgram("force\n", &first);
12204         SetUserThinkingEnables();
12205         break;
12206       case PlayFromGameFile:
12207         (void) StopLoadGameTimer();
12208         if (gameFileFP != NULL) {
12209             gameFileFP = NULL;
12210         }
12211         break;
12212       case EditPosition:
12213         EditPositionDone(TRUE);
12214         break;
12215       case AnalyzeMode:
12216       case AnalyzeFile:
12217         ExitAnalyzeMode();
12218         SendToProgram("force\n", &first);
12219         break;
12220       case TwoMachinesPlay:
12221         GameEnds(EndOfFile, NULL, GE_PLAYER);
12222         ResurrectChessProgram();
12223         SetUserThinkingEnables();
12224         break;
12225       case EndOfGame:
12226         ResurrectChessProgram();
12227         break;
12228       case IcsPlayingBlack:
12229       case IcsPlayingWhite:
12230         DisplayError(_("Warning: You are still playing a game"), 0);
12231         break;
12232       case IcsObserving:
12233         DisplayError(_("Warning: You are still observing a game"), 0);
12234         break;
12235       case IcsExamining:
12236         DisplayError(_("Warning: You are still examining a game"), 0);
12237         break;
12238       case IcsIdle:
12239         break;
12240       case EditGame:
12241       default:
12242         return;
12243     }
12244
12245     pausing = FALSE;
12246     StopClocks();
12247     first.offeredDraw = second.offeredDraw = 0;
12248
12249     if (gameMode == PlayFromGameFile) {
12250         whiteTimeRemaining = timeRemaining[0][currentMove];
12251         blackTimeRemaining = timeRemaining[1][currentMove];
12252         DisplayTitle("");
12253     }
12254
12255     if (gameMode == MachinePlaysWhite ||
12256         gameMode == MachinePlaysBlack ||
12257         gameMode == TwoMachinesPlay ||
12258         gameMode == EndOfGame) {
12259         i = forwardMostMove;
12260         while (i > currentMove) {
12261             SendToProgram("undo\n", &first);
12262             i--;
12263         }
12264         whiteTimeRemaining = timeRemaining[0][currentMove];
12265         blackTimeRemaining = timeRemaining[1][currentMove];
12266         DisplayBothClocks();
12267         if (whiteFlag || blackFlag) {
12268             whiteFlag = blackFlag = 0;
12269         }
12270         DisplayTitle("");
12271     }
12272
12273     gameMode = EditGame;
12274     ModeHighlight();
12275     SetGameInfo();
12276 }
12277
12278
12279 void
12280 EditPositionEvent()
12281 {
12282     if (gameMode == EditPosition) {
12283         EditGameEvent();
12284         return;
12285     }
12286
12287     EditGameEvent();
12288     if (gameMode != EditGame) return;
12289
12290     gameMode = EditPosition;
12291     ModeHighlight();
12292     SetGameInfo();
12293     if (currentMove > 0)
12294       CopyBoard(boards[0], boards[currentMove]);
12295
12296     blackPlaysFirst = !WhiteOnMove(currentMove);
12297     ResetClocks();
12298     currentMove = forwardMostMove = backwardMostMove = 0;
12299     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12300     DisplayMove(-1);
12301 }
12302
12303 void
12304 ExitAnalyzeMode()
12305 {
12306     /* [DM] icsEngineAnalyze - possible call from other functions */
12307     if (appData.icsEngineAnalyze) {
12308         appData.icsEngineAnalyze = FALSE;
12309
12310         DisplayMessage("",_("Close ICS engine analyze..."));
12311     }
12312     if (first.analysisSupport && first.analyzing) {
12313       SendToProgram("exit\n", &first);
12314       first.analyzing = FALSE;
12315     }
12316     thinkOutput[0] = NULLCHAR;
12317 }
12318
12319 void
12320 EditPositionDone(Boolean fakeRights)
12321 {
12322     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12323
12324     startedFromSetupPosition = TRUE;
12325     InitChessProgram(&first, FALSE);
12326     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12327       boards[0][EP_STATUS] = EP_NONE;
12328       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12329     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12330         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12331         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12332       } else boards[0][CASTLING][2] = NoRights;
12333     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12334         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12335         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12336       } else boards[0][CASTLING][5] = NoRights;
12337     }
12338     SendToProgram("force\n", &first);
12339     if (blackPlaysFirst) {
12340         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12341         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12342         currentMove = forwardMostMove = backwardMostMove = 1;
12343         CopyBoard(boards[1], boards[0]);
12344     } else {
12345         currentMove = forwardMostMove = backwardMostMove = 0;
12346     }
12347     SendBoard(&first, forwardMostMove);
12348     if (appData.debugMode) {
12349         fprintf(debugFP, "EditPosDone\n");
12350     }
12351     DisplayTitle("");
12352     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12353     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12354     gameMode = EditGame;
12355     ModeHighlight();
12356     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12357     ClearHighlights(); /* [AS] */
12358 }
12359
12360 /* Pause for `ms' milliseconds */
12361 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12362 void
12363 TimeDelay(ms)
12364      long ms;
12365 {
12366     TimeMark m1, m2;
12367
12368     GetTimeMark(&m1);
12369     do {
12370         GetTimeMark(&m2);
12371     } while (SubtractTimeMarks(&m2, &m1) < ms);
12372 }
12373
12374 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12375 void
12376 SendMultiLineToICS(buf)
12377      char *buf;
12378 {
12379     char temp[MSG_SIZ+1], *p;
12380     int len;
12381
12382     len = strlen(buf);
12383     if (len > MSG_SIZ)
12384       len = MSG_SIZ;
12385
12386     strncpy(temp, buf, len);
12387     temp[len] = 0;
12388
12389     p = temp;
12390     while (*p) {
12391         if (*p == '\n' || *p == '\r')
12392           *p = ' ';
12393         ++p;
12394     }
12395
12396     strcat(temp, "\n");
12397     SendToICS(temp);
12398     SendToPlayer(temp, strlen(temp));
12399 }
12400
12401 void
12402 SetWhiteToPlayEvent()
12403 {
12404     if (gameMode == EditPosition) {
12405         blackPlaysFirst = FALSE;
12406         DisplayBothClocks();    /* works because currentMove is 0 */
12407     } else if (gameMode == IcsExamining) {
12408         SendToICS(ics_prefix);
12409         SendToICS("tomove white\n");
12410     }
12411 }
12412
12413 void
12414 SetBlackToPlayEvent()
12415 {
12416     if (gameMode == EditPosition) {
12417         blackPlaysFirst = TRUE;
12418         currentMove = 1;        /* kludge */
12419         DisplayBothClocks();
12420         currentMove = 0;
12421     } else if (gameMode == IcsExamining) {
12422         SendToICS(ics_prefix);
12423         SendToICS("tomove black\n");
12424     }
12425 }
12426
12427 void
12428 EditPositionMenuEvent(selection, x, y)
12429      ChessSquare selection;
12430      int x, y;
12431 {
12432     char buf[MSG_SIZ];
12433     ChessSquare piece = boards[0][y][x];
12434
12435     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12436
12437     switch (selection) {
12438       case ClearBoard:
12439         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12440             SendToICS(ics_prefix);
12441             SendToICS("bsetup clear\n");
12442         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12443             SendToICS(ics_prefix);
12444             SendToICS("clearboard\n");
12445         } else {
12446             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12447                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12448                 for (y = 0; y < BOARD_HEIGHT; y++) {
12449                     if (gameMode == IcsExamining) {
12450                         if (boards[currentMove][y][x] != EmptySquare) {
12451                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12452                                     AAA + x, ONE + y);
12453                             SendToICS(buf);
12454                         }
12455                     } else {
12456                         boards[0][y][x] = p;
12457                     }
12458                 }
12459             }
12460         }
12461         if (gameMode == EditPosition) {
12462             DrawPosition(FALSE, boards[0]);
12463         }
12464         break;
12465
12466       case WhitePlay:
12467         SetWhiteToPlayEvent();
12468         break;
12469
12470       case BlackPlay:
12471         SetBlackToPlayEvent();
12472         break;
12473
12474       case EmptySquare:
12475         if (gameMode == IcsExamining) {
12476             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12477             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12478             SendToICS(buf);
12479         } else {
12480             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12481                 if(x == BOARD_LEFT-2) {
12482                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12483                     boards[0][y][1] = 0;
12484                 } else
12485                 if(x == BOARD_RGHT+1) {
12486                     if(y >= gameInfo.holdingsSize) break;
12487                     boards[0][y][BOARD_WIDTH-2] = 0;
12488                 } else break;
12489             }
12490             boards[0][y][x] = EmptySquare;
12491             DrawPosition(FALSE, boards[0]);
12492         }
12493         break;
12494
12495       case PromotePiece:
12496         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12497            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12498             selection = (ChessSquare) (PROMOTED piece);
12499         } else if(piece == EmptySquare) selection = WhiteSilver;
12500         else selection = (ChessSquare)((int)piece - 1);
12501         goto defaultlabel;
12502
12503       case DemotePiece:
12504         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12505            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12506             selection = (ChessSquare) (DEMOTED piece);
12507         } else if(piece == EmptySquare) selection = BlackSilver;
12508         else selection = (ChessSquare)((int)piece + 1);
12509         goto defaultlabel;
12510
12511       case WhiteQueen:
12512       case BlackQueen:
12513         if(gameInfo.variant == VariantShatranj ||
12514            gameInfo.variant == VariantXiangqi  ||
12515            gameInfo.variant == VariantCourier  ||
12516            gameInfo.variant == VariantMakruk     )
12517             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12518         goto defaultlabel;
12519
12520       case WhiteKing:
12521       case BlackKing:
12522         if(gameInfo.variant == VariantXiangqi)
12523             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12524         if(gameInfo.variant == VariantKnightmate)
12525             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12526       default:
12527         defaultlabel:
12528         if (gameMode == IcsExamining) {
12529             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12530             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12531                      PieceToChar(selection), AAA + x, ONE + y);
12532             SendToICS(buf);
12533         } else {
12534             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12535                 int n;
12536                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12537                     n = PieceToNumber(selection - BlackPawn);
12538                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12539                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12540                     boards[0][BOARD_HEIGHT-1-n][1]++;
12541                 } else
12542                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12543                     n = PieceToNumber(selection);
12544                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12545                     boards[0][n][BOARD_WIDTH-1] = selection;
12546                     boards[0][n][BOARD_WIDTH-2]++;
12547                 }
12548             } else
12549             boards[0][y][x] = selection;
12550             DrawPosition(TRUE, boards[0]);
12551         }
12552         break;
12553     }
12554 }
12555
12556
12557 void
12558 DropMenuEvent(selection, x, y)
12559      ChessSquare selection;
12560      int x, y;
12561 {
12562     ChessMove moveType;
12563
12564     switch (gameMode) {
12565       case IcsPlayingWhite:
12566       case MachinePlaysBlack:
12567         if (!WhiteOnMove(currentMove)) {
12568             DisplayMoveError(_("It is Black's turn"));
12569             return;
12570         }
12571         moveType = WhiteDrop;
12572         break;
12573       case IcsPlayingBlack:
12574       case MachinePlaysWhite:
12575         if (WhiteOnMove(currentMove)) {
12576             DisplayMoveError(_("It is White's turn"));
12577             return;
12578         }
12579         moveType = BlackDrop;
12580         break;
12581       case EditGame:
12582         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12583         break;
12584       default:
12585         return;
12586     }
12587
12588     if (moveType == BlackDrop && selection < BlackPawn) {
12589       selection = (ChessSquare) ((int) selection
12590                                  + (int) BlackPawn - (int) WhitePawn);
12591     }
12592     if (boards[currentMove][y][x] != EmptySquare) {
12593         DisplayMoveError(_("That square is occupied"));
12594         return;
12595     }
12596
12597     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12598 }
12599
12600 void
12601 AcceptEvent()
12602 {
12603     /* Accept a pending offer of any kind from opponent */
12604
12605     if (appData.icsActive) {
12606         SendToICS(ics_prefix);
12607         SendToICS("accept\n");
12608     } else if (cmailMsgLoaded) {
12609         if (currentMove == cmailOldMove &&
12610             commentList[cmailOldMove] != NULL &&
12611             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12612                    "Black offers a draw" : "White offers a draw")) {
12613             TruncateGame();
12614             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12615             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12616         } else {
12617             DisplayError(_("There is no pending offer on this move"), 0);
12618             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12619         }
12620     } else {
12621         /* Not used for offers from chess program */
12622     }
12623 }
12624
12625 void
12626 DeclineEvent()
12627 {
12628     /* Decline a pending offer of any kind from opponent */
12629
12630     if (appData.icsActive) {
12631         SendToICS(ics_prefix);
12632         SendToICS("decline\n");
12633     } else if (cmailMsgLoaded) {
12634         if (currentMove == cmailOldMove &&
12635             commentList[cmailOldMove] != NULL &&
12636             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12637                    "Black offers a draw" : "White offers a draw")) {
12638 #ifdef NOTDEF
12639             AppendComment(cmailOldMove, "Draw declined", TRUE);
12640             DisplayComment(cmailOldMove - 1, "Draw declined");
12641 #endif /*NOTDEF*/
12642         } else {
12643             DisplayError(_("There is no pending offer on this move"), 0);
12644         }
12645     } else {
12646         /* Not used for offers from chess program */
12647     }
12648 }
12649
12650 void
12651 RematchEvent()
12652 {
12653     /* Issue ICS rematch command */
12654     if (appData.icsActive) {
12655         SendToICS(ics_prefix);
12656         SendToICS("rematch\n");
12657     }
12658 }
12659
12660 void
12661 CallFlagEvent()
12662 {
12663     /* Call your opponent's flag (claim a win on time) */
12664     if (appData.icsActive) {
12665         SendToICS(ics_prefix);
12666         SendToICS("flag\n");
12667     } else {
12668         switch (gameMode) {
12669           default:
12670             return;
12671           case MachinePlaysWhite:
12672             if (whiteFlag) {
12673                 if (blackFlag)
12674                   GameEnds(GameIsDrawn, "Both players ran out of time",
12675                            GE_PLAYER);
12676                 else
12677                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12678             } else {
12679                 DisplayError(_("Your opponent is not out of time"), 0);
12680             }
12681             break;
12682           case MachinePlaysBlack:
12683             if (blackFlag) {
12684                 if (whiteFlag)
12685                   GameEnds(GameIsDrawn, "Both players ran out of time",
12686                            GE_PLAYER);
12687                 else
12688                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12689             } else {
12690                 DisplayError(_("Your opponent is not out of time"), 0);
12691             }
12692             break;
12693         }
12694     }
12695 }
12696
12697 void
12698 ClockClick(int which)
12699 {       // [HGM] code moved to back-end from winboard.c
12700         if(which) { // black clock
12701           if (gameMode == EditPosition || gameMode == IcsExamining) {
12702             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12703             SetBlackToPlayEvent();
12704           } else if (gameMode == EditGame || shiftKey) {
12705             AdjustClock(which, -1);
12706           } else if (gameMode == IcsPlayingWhite ||
12707                      gameMode == MachinePlaysBlack) {
12708             CallFlagEvent();
12709           }
12710         } else { // white clock
12711           if (gameMode == EditPosition || gameMode == IcsExamining) {
12712             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12713             SetWhiteToPlayEvent();
12714           } else if (gameMode == EditGame || shiftKey) {
12715             AdjustClock(which, -1);
12716           } else if (gameMode == IcsPlayingBlack ||
12717                    gameMode == MachinePlaysWhite) {
12718             CallFlagEvent();
12719           }
12720         }
12721 }
12722
12723 void
12724 DrawEvent()
12725 {
12726     /* Offer draw or accept pending draw offer from opponent */
12727
12728     if (appData.icsActive) {
12729         /* Note: tournament rules require draw offers to be
12730            made after you make your move but before you punch
12731            your clock.  Currently ICS doesn't let you do that;
12732            instead, you immediately punch your clock after making
12733            a move, but you can offer a draw at any time. */
12734
12735         SendToICS(ics_prefix);
12736         SendToICS("draw\n");
12737         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12738     } else if (cmailMsgLoaded) {
12739         if (currentMove == cmailOldMove &&
12740             commentList[cmailOldMove] != NULL &&
12741             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12742                    "Black offers a draw" : "White offers a draw")) {
12743             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12744             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12745         } else if (currentMove == cmailOldMove + 1) {
12746             char *offer = WhiteOnMove(cmailOldMove) ?
12747               "White offers a draw" : "Black offers a draw";
12748             AppendComment(currentMove, offer, TRUE);
12749             DisplayComment(currentMove - 1, offer);
12750             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12751         } else {
12752             DisplayError(_("You must make your move before offering a draw"), 0);
12753             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12754         }
12755     } else if (first.offeredDraw) {
12756         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12757     } else {
12758         if (first.sendDrawOffers) {
12759             SendToProgram("draw\n", &first);
12760             userOfferedDraw = TRUE;
12761         }
12762     }
12763 }
12764
12765 void
12766 AdjournEvent()
12767 {
12768     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12769
12770     if (appData.icsActive) {
12771         SendToICS(ics_prefix);
12772         SendToICS("adjourn\n");
12773     } else {
12774         /* Currently GNU Chess doesn't offer or accept Adjourns */
12775     }
12776 }
12777
12778
12779 void
12780 AbortEvent()
12781 {
12782     /* Offer Abort or accept pending Abort offer from opponent */
12783
12784     if (appData.icsActive) {
12785         SendToICS(ics_prefix);
12786         SendToICS("abort\n");
12787     } else {
12788         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12789     }
12790 }
12791
12792 void
12793 ResignEvent()
12794 {
12795     /* Resign.  You can do this even if it's not your turn. */
12796
12797     if (appData.icsActive) {
12798         SendToICS(ics_prefix);
12799         SendToICS("resign\n");
12800     } else {
12801         switch (gameMode) {
12802           case MachinePlaysWhite:
12803             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12804             break;
12805           case MachinePlaysBlack:
12806             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12807             break;
12808           case EditGame:
12809             if (cmailMsgLoaded) {
12810                 TruncateGame();
12811                 if (WhiteOnMove(cmailOldMove)) {
12812                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12813                 } else {
12814                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12815                 }
12816                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12817             }
12818             break;
12819           default:
12820             break;
12821         }
12822     }
12823 }
12824
12825
12826 void
12827 StopObservingEvent()
12828 {
12829     /* Stop observing current games */
12830     SendToICS(ics_prefix);
12831     SendToICS("unobserve\n");
12832 }
12833
12834 void
12835 StopExaminingEvent()
12836 {
12837     /* Stop observing current game */
12838     SendToICS(ics_prefix);
12839     SendToICS("unexamine\n");
12840 }
12841
12842 void
12843 ForwardInner(target)
12844      int target;
12845 {
12846     int limit;
12847
12848     if (appData.debugMode)
12849         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12850                 target, currentMove, forwardMostMove);
12851
12852     if (gameMode == EditPosition)
12853       return;
12854
12855     if (gameMode == PlayFromGameFile && !pausing)
12856       PauseEvent();
12857
12858     if (gameMode == IcsExamining && pausing)
12859       limit = pauseExamForwardMostMove;
12860     else
12861       limit = forwardMostMove;
12862
12863     if (target > limit) target = limit;
12864
12865     if (target > 0 && moveList[target - 1][0]) {
12866         int fromX, fromY, toX, toY;
12867         toX = moveList[target - 1][2] - AAA;
12868         toY = moveList[target - 1][3] - ONE;
12869         if (moveList[target - 1][1] == '@') {
12870             if (appData.highlightLastMove) {
12871                 SetHighlights(-1, -1, toX, toY);
12872             }
12873         } else {
12874             fromX = moveList[target - 1][0] - AAA;
12875             fromY = moveList[target - 1][1] - ONE;
12876             if (target == currentMove + 1) {
12877                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12878             }
12879             if (appData.highlightLastMove) {
12880                 SetHighlights(fromX, fromY, toX, toY);
12881             }
12882         }
12883     }
12884     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12885         gameMode == Training || gameMode == PlayFromGameFile ||
12886         gameMode == AnalyzeFile) {
12887         while (currentMove < target) {
12888             SendMoveToProgram(currentMove++, &first);
12889         }
12890     } else {
12891         currentMove = target;
12892     }
12893
12894     if (gameMode == EditGame || gameMode == EndOfGame) {
12895         whiteTimeRemaining = timeRemaining[0][currentMove];
12896         blackTimeRemaining = timeRemaining[1][currentMove];
12897     }
12898     DisplayBothClocks();
12899     DisplayMove(currentMove - 1);
12900     DrawPosition(FALSE, boards[currentMove]);
12901     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12902     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12903         DisplayComment(currentMove - 1, commentList[currentMove]);
12904     }
12905 }
12906
12907
12908 void
12909 ForwardEvent()
12910 {
12911     if (gameMode == IcsExamining && !pausing) {
12912         SendToICS(ics_prefix);
12913         SendToICS("forward\n");
12914     } else {
12915         ForwardInner(currentMove + 1);
12916     }
12917 }
12918
12919 void
12920 ToEndEvent()
12921 {
12922     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12923         /* to optimze, we temporarily turn off analysis mode while we feed
12924          * the remaining moves to the engine. Otherwise we get analysis output
12925          * after each move.
12926          */
12927         if (first.analysisSupport) {
12928           SendToProgram("exit\nforce\n", &first);
12929           first.analyzing = FALSE;
12930         }
12931     }
12932
12933     if (gameMode == IcsExamining && !pausing) {
12934         SendToICS(ics_prefix);
12935         SendToICS("forward 999999\n");
12936     } else {
12937         ForwardInner(forwardMostMove);
12938     }
12939
12940     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12941         /* we have fed all the moves, so reactivate analysis mode */
12942         SendToProgram("analyze\n", &first);
12943         first.analyzing = TRUE;
12944         /*first.maybeThinking = TRUE;*/
12945         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12946     }
12947 }
12948
12949 void
12950 BackwardInner(target)
12951      int target;
12952 {
12953     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12954
12955     if (appData.debugMode)
12956         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12957                 target, currentMove, forwardMostMove);
12958
12959     if (gameMode == EditPosition) return;
12960     if (currentMove <= backwardMostMove) {
12961         ClearHighlights();
12962         DrawPosition(full_redraw, boards[currentMove]);
12963         return;
12964     }
12965     if (gameMode == PlayFromGameFile && !pausing)
12966       PauseEvent();
12967
12968     if (moveList[target][0]) {
12969         int fromX, fromY, toX, toY;
12970         toX = moveList[target][2] - AAA;
12971         toY = moveList[target][3] - ONE;
12972         if (moveList[target][1] == '@') {
12973             if (appData.highlightLastMove) {
12974                 SetHighlights(-1, -1, toX, toY);
12975             }
12976         } else {
12977             fromX = moveList[target][0] - AAA;
12978             fromY = moveList[target][1] - ONE;
12979             if (target == currentMove - 1) {
12980                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12981             }
12982             if (appData.highlightLastMove) {
12983                 SetHighlights(fromX, fromY, toX, toY);
12984             }
12985         }
12986     }
12987     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12988         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12989         while (currentMove > target) {
12990             SendToProgram("undo\n", &first);
12991             currentMove--;
12992         }
12993     } else {
12994         currentMove = target;
12995     }
12996
12997     if (gameMode == EditGame || gameMode == EndOfGame) {
12998         whiteTimeRemaining = timeRemaining[0][currentMove];
12999         blackTimeRemaining = timeRemaining[1][currentMove];
13000     }
13001     DisplayBothClocks();
13002     DisplayMove(currentMove - 1);
13003     DrawPosition(full_redraw, boards[currentMove]);
13004     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13005     // [HGM] PV info: routine tests if comment empty
13006     DisplayComment(currentMove - 1, commentList[currentMove]);
13007 }
13008
13009 void
13010 BackwardEvent()
13011 {
13012     if (gameMode == IcsExamining && !pausing) {
13013         SendToICS(ics_prefix);
13014         SendToICS("backward\n");
13015     } else {
13016         BackwardInner(currentMove - 1);
13017     }
13018 }
13019
13020 void
13021 ToStartEvent()
13022 {
13023     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13024         /* to optimize, we temporarily turn off analysis mode while we undo
13025          * all the moves. Otherwise we get analysis output after each undo.
13026          */
13027         if (first.analysisSupport) {
13028           SendToProgram("exit\nforce\n", &first);
13029           first.analyzing = FALSE;
13030         }
13031     }
13032
13033     if (gameMode == IcsExamining && !pausing) {
13034         SendToICS(ics_prefix);
13035         SendToICS("backward 999999\n");
13036     } else {
13037         BackwardInner(backwardMostMove);
13038     }
13039
13040     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13041         /* we have fed all the moves, so reactivate analysis mode */
13042         SendToProgram("analyze\n", &first);
13043         first.analyzing = TRUE;
13044         /*first.maybeThinking = TRUE;*/
13045         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13046     }
13047 }
13048
13049 void
13050 ToNrEvent(int to)
13051 {
13052   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13053   if (to >= forwardMostMove) to = forwardMostMove;
13054   if (to <= backwardMostMove) to = backwardMostMove;
13055   if (to < currentMove) {
13056     BackwardInner(to);
13057   } else {
13058     ForwardInner(to);
13059   }
13060 }
13061
13062 void
13063 RevertEvent(Boolean annotate)
13064 {
13065     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13066         return;
13067     }
13068     if (gameMode != IcsExamining) {
13069         DisplayError(_("You are not examining a game"), 0);
13070         return;
13071     }
13072     if (pausing) {
13073         DisplayError(_("You can't revert while pausing"), 0);
13074         return;
13075     }
13076     SendToICS(ics_prefix);
13077     SendToICS("revert\n");
13078 }
13079
13080 void
13081 RetractMoveEvent()
13082 {
13083     switch (gameMode) {
13084       case MachinePlaysWhite:
13085       case MachinePlaysBlack:
13086         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13087             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13088             return;
13089         }
13090         if (forwardMostMove < 2) return;
13091         currentMove = forwardMostMove = forwardMostMove - 2;
13092         whiteTimeRemaining = timeRemaining[0][currentMove];
13093         blackTimeRemaining = timeRemaining[1][currentMove];
13094         DisplayBothClocks();
13095         DisplayMove(currentMove - 1);
13096         ClearHighlights();/*!! could figure this out*/
13097         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13098         SendToProgram("remove\n", &first);
13099         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13100         break;
13101
13102       case BeginningOfGame:
13103       default:
13104         break;
13105
13106       case IcsPlayingWhite:
13107       case IcsPlayingBlack:
13108         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13109             SendToICS(ics_prefix);
13110             SendToICS("takeback 2\n");
13111         } else {
13112             SendToICS(ics_prefix);
13113             SendToICS("takeback 1\n");
13114         }
13115         break;
13116     }
13117 }
13118
13119 void
13120 MoveNowEvent()
13121 {
13122     ChessProgramState *cps;
13123
13124     switch (gameMode) {
13125       case MachinePlaysWhite:
13126         if (!WhiteOnMove(forwardMostMove)) {
13127             DisplayError(_("It is your turn"), 0);
13128             return;
13129         }
13130         cps = &first;
13131         break;
13132       case MachinePlaysBlack:
13133         if (WhiteOnMove(forwardMostMove)) {
13134             DisplayError(_("It is your turn"), 0);
13135             return;
13136         }
13137         cps = &first;
13138         break;
13139       case TwoMachinesPlay:
13140         if (WhiteOnMove(forwardMostMove) ==
13141             (first.twoMachinesColor[0] == 'w')) {
13142             cps = &first;
13143         } else {
13144             cps = &second;
13145         }
13146         break;
13147       case BeginningOfGame:
13148       default:
13149         return;
13150     }
13151     SendToProgram("?\n", cps);
13152 }
13153
13154 void
13155 TruncateGameEvent()
13156 {
13157     EditGameEvent();
13158     if (gameMode != EditGame) return;
13159     TruncateGame();
13160 }
13161
13162 void
13163 TruncateGame()
13164 {
13165     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13166     if (forwardMostMove > currentMove) {
13167         if (gameInfo.resultDetails != NULL) {
13168             free(gameInfo.resultDetails);
13169             gameInfo.resultDetails = NULL;
13170             gameInfo.result = GameUnfinished;
13171         }
13172         forwardMostMove = currentMove;
13173         HistorySet(parseList, backwardMostMove, forwardMostMove,
13174                    currentMove-1);
13175     }
13176 }
13177
13178 void
13179 HintEvent()
13180 {
13181     if (appData.noChessProgram) return;
13182     switch (gameMode) {
13183       case MachinePlaysWhite:
13184         if (WhiteOnMove(forwardMostMove)) {
13185             DisplayError(_("Wait until your turn"), 0);
13186             return;
13187         }
13188         break;
13189       case BeginningOfGame:
13190       case MachinePlaysBlack:
13191         if (!WhiteOnMove(forwardMostMove)) {
13192             DisplayError(_("Wait until your turn"), 0);
13193             return;
13194         }
13195         break;
13196       default:
13197         DisplayError(_("No hint available"), 0);
13198         return;
13199     }
13200     SendToProgram("hint\n", &first);
13201     hintRequested = TRUE;
13202 }
13203
13204 void
13205 BookEvent()
13206 {
13207     if (appData.noChessProgram) return;
13208     switch (gameMode) {
13209       case MachinePlaysWhite:
13210         if (WhiteOnMove(forwardMostMove)) {
13211             DisplayError(_("Wait until your turn"), 0);
13212             return;
13213         }
13214         break;
13215       case BeginningOfGame:
13216       case MachinePlaysBlack:
13217         if (!WhiteOnMove(forwardMostMove)) {
13218             DisplayError(_("Wait until your turn"), 0);
13219             return;
13220         }
13221         break;
13222       case EditPosition:
13223         EditPositionDone(TRUE);
13224         break;
13225       case TwoMachinesPlay:
13226         return;
13227       default:
13228         break;
13229     }
13230     SendToProgram("bk\n", &first);
13231     bookOutput[0] = NULLCHAR;
13232     bookRequested = TRUE;
13233 }
13234
13235 void
13236 AboutGameEvent()
13237 {
13238     char *tags = PGNTags(&gameInfo);
13239     TagsPopUp(tags, CmailMsg());
13240     free(tags);
13241 }
13242
13243 /* end button procedures */
13244
13245 void
13246 PrintPosition(fp, move)
13247      FILE *fp;
13248      int move;
13249 {
13250     int i, j;
13251
13252     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13253         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13254             char c = PieceToChar(boards[move][i][j]);
13255             fputc(c == 'x' ? '.' : c, fp);
13256             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13257         }
13258     }
13259     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13260       fprintf(fp, "white to play\n");
13261     else
13262       fprintf(fp, "black to play\n");
13263 }
13264
13265 void
13266 PrintOpponents(fp)
13267      FILE *fp;
13268 {
13269     if (gameInfo.white != NULL) {
13270         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13271     } else {
13272         fprintf(fp, "\n");
13273     }
13274 }
13275
13276 /* Find last component of program's own name, using some heuristics */
13277 void
13278 TidyProgramName(prog, host, buf)
13279      char *prog, *host, buf[MSG_SIZ];
13280 {
13281     char *p, *q;
13282     int local = (strcmp(host, "localhost") == 0);
13283     while (!local && (p = strchr(prog, ';')) != NULL) {
13284         p++;
13285         while (*p == ' ') p++;
13286         prog = p;
13287     }
13288     if (*prog == '"' || *prog == '\'') {
13289         q = strchr(prog + 1, *prog);
13290     } else {
13291         q = strchr(prog, ' ');
13292     }
13293     if (q == NULL) q = prog + strlen(prog);
13294     p = q;
13295     while (p >= prog && *p != '/' && *p != '\\') p--;
13296     p++;
13297     if(p == prog && *p == '"') p++;
13298     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13299     memcpy(buf, p, q - p);
13300     buf[q - p] = NULLCHAR;
13301     if (!local) {
13302         strcat(buf, "@");
13303         strcat(buf, host);
13304     }
13305 }
13306
13307 char *
13308 TimeControlTagValue()
13309 {
13310     char buf[MSG_SIZ];
13311     if (!appData.clockMode) {
13312       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13313     } else if (movesPerSession > 0) {
13314       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13315     } else if (timeIncrement == 0) {
13316       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13317     } else {
13318       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13319     }
13320     return StrSave(buf);
13321 }
13322
13323 void
13324 SetGameInfo()
13325 {
13326     /* This routine is used only for certain modes */
13327     VariantClass v = gameInfo.variant;
13328     ChessMove r = GameUnfinished;
13329     char *p = NULL;
13330
13331     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13332         r = gameInfo.result;
13333         p = gameInfo.resultDetails;
13334         gameInfo.resultDetails = NULL;
13335     }
13336     ClearGameInfo(&gameInfo);
13337     gameInfo.variant = v;
13338
13339     switch (gameMode) {
13340       case MachinePlaysWhite:
13341         gameInfo.event = StrSave( appData.pgnEventHeader );
13342         gameInfo.site = StrSave(HostName());
13343         gameInfo.date = PGNDate();
13344         gameInfo.round = StrSave("-");
13345         gameInfo.white = StrSave(first.tidy);
13346         gameInfo.black = StrSave(UserName());
13347         gameInfo.timeControl = TimeControlTagValue();
13348         break;
13349
13350       case MachinePlaysBlack:
13351         gameInfo.event = StrSave( appData.pgnEventHeader );
13352         gameInfo.site = StrSave(HostName());
13353         gameInfo.date = PGNDate();
13354         gameInfo.round = StrSave("-");
13355         gameInfo.white = StrSave(UserName());
13356         gameInfo.black = StrSave(first.tidy);
13357         gameInfo.timeControl = TimeControlTagValue();
13358         break;
13359
13360       case TwoMachinesPlay:
13361         gameInfo.event = StrSave( appData.pgnEventHeader );
13362         gameInfo.site = StrSave(HostName());
13363         gameInfo.date = PGNDate();
13364         if (matchGame > 0) {
13365             char buf[MSG_SIZ];
13366             snprintf(buf, MSG_SIZ, "%d", matchGame);
13367             gameInfo.round = StrSave(buf);
13368         } else {
13369             gameInfo.round = StrSave("-");
13370         }
13371         if (first.twoMachinesColor[0] == 'w') {
13372             gameInfo.white = StrSave(first.tidy);
13373             gameInfo.black = StrSave(second.tidy);
13374         } else {
13375             gameInfo.white = StrSave(second.tidy);
13376             gameInfo.black = StrSave(first.tidy);
13377         }
13378         gameInfo.timeControl = TimeControlTagValue();
13379         break;
13380
13381       case EditGame:
13382         gameInfo.event = StrSave("Edited game");
13383         gameInfo.site = StrSave(HostName());
13384         gameInfo.date = PGNDate();
13385         gameInfo.round = StrSave("-");
13386         gameInfo.white = StrSave("-");
13387         gameInfo.black = StrSave("-");
13388         gameInfo.result = r;
13389         gameInfo.resultDetails = p;
13390         break;
13391
13392       case EditPosition:
13393         gameInfo.event = StrSave("Edited position");
13394         gameInfo.site = StrSave(HostName());
13395         gameInfo.date = PGNDate();
13396         gameInfo.round = StrSave("-");
13397         gameInfo.white = StrSave("-");
13398         gameInfo.black = StrSave("-");
13399         break;
13400
13401       case IcsPlayingWhite:
13402       case IcsPlayingBlack:
13403       case IcsObserving:
13404       case IcsExamining:
13405         break;
13406
13407       case PlayFromGameFile:
13408         gameInfo.event = StrSave("Game from non-PGN file");
13409         gameInfo.site = StrSave(HostName());
13410         gameInfo.date = PGNDate();
13411         gameInfo.round = StrSave("-");
13412         gameInfo.white = StrSave("?");
13413         gameInfo.black = StrSave("?");
13414         break;
13415
13416       default:
13417         break;
13418     }
13419 }
13420
13421 void
13422 ReplaceComment(index, text)
13423      int index;
13424      char *text;
13425 {
13426     int len;
13427     char *p;
13428     float score;
13429
13430     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13431        pvInfoList[index-1].depth == len &&
13432        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13433        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13434     while (*text == '\n') text++;
13435     len = strlen(text);
13436     while (len > 0 && text[len - 1] == '\n') len--;
13437
13438     if (commentList[index] != NULL)
13439       free(commentList[index]);
13440
13441     if (len == 0) {
13442         commentList[index] = NULL;
13443         return;
13444     }
13445   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13446       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13447       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13448     commentList[index] = (char *) malloc(len + 2);
13449     strncpy(commentList[index], text, len);
13450     commentList[index][len] = '\n';
13451     commentList[index][len + 1] = NULLCHAR;
13452   } else {
13453     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13454     char *p;
13455     commentList[index] = (char *) malloc(len + 7);
13456     safeStrCpy(commentList[index], "{\n", 3);
13457     safeStrCpy(commentList[index]+2, text, len+1);
13458     commentList[index][len+2] = NULLCHAR;
13459     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13460     strcat(commentList[index], "\n}\n");
13461   }
13462 }
13463
13464 void
13465 CrushCRs(text)
13466      char *text;
13467 {
13468   char *p = text;
13469   char *q = text;
13470   char ch;
13471
13472   do {
13473     ch = *p++;
13474     if (ch == '\r') continue;
13475     *q++ = ch;
13476   } while (ch != '\0');
13477 }
13478
13479 void
13480 AppendComment(index, text, addBraces)
13481      int index;
13482      char *text;
13483      Boolean addBraces; // [HGM] braces: tells if we should add {}
13484 {
13485     int oldlen, len;
13486     char *old;
13487
13488 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13489     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13490
13491     CrushCRs(text);
13492     while (*text == '\n') text++;
13493     len = strlen(text);
13494     while (len > 0 && text[len - 1] == '\n') len--;
13495
13496     if (len == 0) return;
13497
13498     if (commentList[index] != NULL) {
13499         old = commentList[index];
13500         oldlen = strlen(old);
13501         while(commentList[index][oldlen-1] ==  '\n')
13502           commentList[index][--oldlen] = NULLCHAR;
13503         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13504         safeStrCpy(commentList[index], old, oldlen + len + 6);
13505         free(old);
13506         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13507         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13508           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13509           while (*text == '\n') { text++; len--; }
13510           commentList[index][--oldlen] = NULLCHAR;
13511       }
13512         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13513         else          strcat(commentList[index], "\n");
13514         strcat(commentList[index], text);
13515         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13516         else          strcat(commentList[index], "\n");
13517     } else {
13518         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13519         if(addBraces)
13520           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13521         else commentList[index][0] = NULLCHAR;
13522         strcat(commentList[index], text);
13523         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13524         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13525     }
13526 }
13527
13528 static char * FindStr( char * text, char * sub_text )
13529 {
13530     char * result = strstr( text, sub_text );
13531
13532     if( result != NULL ) {
13533         result += strlen( sub_text );
13534     }
13535
13536     return result;
13537 }
13538
13539 /* [AS] Try to extract PV info from PGN comment */
13540 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13541 char *GetInfoFromComment( int index, char * text )
13542 {
13543     char * sep = text, *p;
13544
13545     if( text != NULL && index > 0 ) {
13546         int score = 0;
13547         int depth = 0;
13548         int time = -1, sec = 0, deci;
13549         char * s_eval = FindStr( text, "[%eval " );
13550         char * s_emt = FindStr( text, "[%emt " );
13551
13552         if( s_eval != NULL || s_emt != NULL ) {
13553             /* New style */
13554             char delim;
13555
13556             if( s_eval != NULL ) {
13557                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13558                     return text;
13559                 }
13560
13561                 if( delim != ']' ) {
13562                     return text;
13563                 }
13564             }
13565
13566             if( s_emt != NULL ) {
13567             }
13568                 return text;
13569         }
13570         else {
13571             /* We expect something like: [+|-]nnn.nn/dd */
13572             int score_lo = 0;
13573
13574             if(*text != '{') return text; // [HGM] braces: must be normal comment
13575
13576             sep = strchr( text, '/' );
13577             if( sep == NULL || sep < (text+4) ) {
13578                 return text;
13579             }
13580
13581             p = text;
13582             if(p[1] == '(') { // comment starts with PV
13583                p = strchr(p, ')'); // locate end of PV
13584                if(p == NULL || sep < p+5) return text;
13585                // at this point we have something like "{(.*) +0.23/6 ..."
13586                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13587                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13588                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13589             }
13590             time = -1; sec = -1; deci = -1;
13591             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13592                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13593                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13594                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13595                 return text;
13596             }
13597
13598             if( score_lo < 0 || score_lo >= 100 ) {
13599                 return text;
13600             }
13601
13602             if(sec >= 0) time = 600*time + 10*sec; else
13603             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13604
13605             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13606
13607             /* [HGM] PV time: now locate end of PV info */
13608             while( *++sep >= '0' && *sep <= '9'); // strip depth
13609             if(time >= 0)
13610             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13611             if(sec >= 0)
13612             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13613             if(deci >= 0)
13614             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13615             while(*sep == ' ') sep++;
13616         }
13617
13618         if( depth <= 0 ) {
13619             return text;
13620         }
13621
13622         if( time < 0 ) {
13623             time = -1;
13624         }
13625
13626         pvInfoList[index-1].depth = depth;
13627         pvInfoList[index-1].score = score;
13628         pvInfoList[index-1].time  = 10*time; // centi-sec
13629         if(*sep == '}') *sep = 0; else *--sep = '{';
13630         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13631     }
13632     return sep;
13633 }
13634
13635 void
13636 SendToProgram(message, cps)
13637      char *message;
13638      ChessProgramState *cps;
13639 {
13640     int count, outCount, error;
13641     char buf[MSG_SIZ];
13642
13643     if (cps->pr == NULL) return;
13644     Attention(cps);
13645
13646     if (appData.debugMode) {
13647         TimeMark now;
13648         GetTimeMark(&now);
13649         fprintf(debugFP, "%ld >%-6s: %s",
13650                 SubtractTimeMarks(&now, &programStartTime),
13651                 cps->which, message);
13652     }
13653
13654     count = strlen(message);
13655     outCount = OutputToProcess(cps->pr, message, count, &error);
13656     if (outCount < count && !exiting
13657                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13658       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13659         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13660             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13661                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13662                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13663             } else {
13664                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13665             }
13666             gameInfo.resultDetails = StrSave(buf);
13667         }
13668         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13669     }
13670 }
13671
13672 void
13673 ReceiveFromProgram(isr, closure, message, count, error)
13674      InputSourceRef isr;
13675      VOIDSTAR closure;
13676      char *message;
13677      int count;
13678      int error;
13679 {
13680     char *end_str;
13681     char buf[MSG_SIZ];
13682     ChessProgramState *cps = (ChessProgramState *)closure;
13683
13684     if (isr != cps->isr) return; /* Killed intentionally */
13685     if (count <= 0) {
13686         if (count == 0) {
13687             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13688                     _(cps->which), cps->program);
13689         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13690                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13691                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13692                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13693                 } else {
13694                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13695                 }
13696                 gameInfo.resultDetails = StrSave(buf);
13697             }
13698             RemoveInputSource(cps->isr);
13699             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13700         } else {
13701             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13702                     _(cps->which), cps->program);
13703             RemoveInputSource(cps->isr);
13704
13705             /* [AS] Program is misbehaving badly... kill it */
13706             if( count == -2 ) {
13707                 DestroyChildProcess( cps->pr, 9 );
13708                 cps->pr = NoProc;
13709             }
13710
13711             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13712         }
13713         return;
13714     }
13715
13716     if ((end_str = strchr(message, '\r')) != NULL)
13717       *end_str = NULLCHAR;
13718     if ((end_str = strchr(message, '\n')) != NULL)
13719       *end_str = NULLCHAR;
13720
13721     if (appData.debugMode) {
13722         TimeMark now; int print = 1;
13723         char *quote = ""; char c; int i;
13724
13725         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13726                 char start = message[0];
13727                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13728                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13729                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13730                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13731                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13732                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13733                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13734                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13735                    sscanf(message, "hint: %c", &c)!=1 && 
13736                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13737                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13738                     print = (appData.engineComments >= 2);
13739                 }
13740                 message[0] = start; // restore original message
13741         }
13742         if(print) {
13743                 GetTimeMark(&now);
13744                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13745                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13746                         quote,
13747                         message);
13748         }
13749     }
13750
13751     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13752     if (appData.icsEngineAnalyze) {
13753         if (strstr(message, "whisper") != NULL ||
13754              strstr(message, "kibitz") != NULL ||
13755             strstr(message, "tellics") != NULL) return;
13756     }
13757
13758     HandleMachineMove(message, cps);
13759 }
13760
13761
13762 void
13763 SendTimeControl(cps, mps, tc, inc, sd, st)
13764      ChessProgramState *cps;
13765      int mps, inc, sd, st;
13766      long tc;
13767 {
13768     char buf[MSG_SIZ];
13769     int seconds;
13770
13771     if( timeControl_2 > 0 ) {
13772         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13773             tc = timeControl_2;
13774         }
13775     }
13776     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13777     inc /= cps->timeOdds;
13778     st  /= cps->timeOdds;
13779
13780     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13781
13782     if (st > 0) {
13783       /* Set exact time per move, normally using st command */
13784       if (cps->stKludge) {
13785         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13786         seconds = st % 60;
13787         if (seconds == 0) {
13788           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13789         } else {
13790           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13791         }
13792       } else {
13793         snprintf(buf, MSG_SIZ, "st %d\n", st);
13794       }
13795     } else {
13796       /* Set conventional or incremental time control, using level command */
13797       if (seconds == 0) {
13798         /* Note old gnuchess bug -- minutes:seconds used to not work.
13799            Fixed in later versions, but still avoid :seconds
13800            when seconds is 0. */
13801         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13802       } else {
13803         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13804                  seconds, inc/1000.);
13805       }
13806     }
13807     SendToProgram(buf, cps);
13808
13809     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13810     /* Orthogonally, limit search to given depth */
13811     if (sd > 0) {
13812       if (cps->sdKludge) {
13813         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13814       } else {
13815         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13816       }
13817       SendToProgram(buf, cps);
13818     }
13819
13820     if(cps->nps >= 0) { /* [HGM] nps */
13821         if(cps->supportsNPS == FALSE)
13822           cps->nps = -1; // don't use if engine explicitly says not supported!
13823         else {
13824           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13825           SendToProgram(buf, cps);
13826         }
13827     }
13828 }
13829
13830 ChessProgramState *WhitePlayer()
13831 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13832 {
13833     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13834        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13835         return &second;
13836     return &first;
13837 }
13838
13839 void
13840 SendTimeRemaining(cps, machineWhite)
13841      ChessProgramState *cps;
13842      int /*boolean*/ machineWhite;
13843 {
13844     char message[MSG_SIZ];
13845     long time, otime;
13846
13847     /* Note: this routine must be called when the clocks are stopped
13848        or when they have *just* been set or switched; otherwise
13849        it will be off by the time since the current tick started.
13850     */
13851     if (machineWhite) {
13852         time = whiteTimeRemaining / 10;
13853         otime = blackTimeRemaining / 10;
13854     } else {
13855         time = blackTimeRemaining / 10;
13856         otime = whiteTimeRemaining / 10;
13857     }
13858     /* [HGM] translate opponent's time by time-odds factor */
13859     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13860     if (appData.debugMode) {
13861         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13862     }
13863
13864     if (time <= 0) time = 1;
13865     if (otime <= 0) otime = 1;
13866
13867     snprintf(message, MSG_SIZ, "time %ld\n", time);
13868     SendToProgram(message, cps);
13869
13870     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13871     SendToProgram(message, cps);
13872 }
13873
13874 int
13875 BoolFeature(p, name, loc, cps)
13876      char **p;
13877      char *name;
13878      int *loc;
13879      ChessProgramState *cps;
13880 {
13881   char buf[MSG_SIZ];
13882   int len = strlen(name);
13883   int val;
13884
13885   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13886     (*p) += len + 1;
13887     sscanf(*p, "%d", &val);
13888     *loc = (val != 0);
13889     while (**p && **p != ' ')
13890       (*p)++;
13891     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13892     SendToProgram(buf, cps);
13893     return TRUE;
13894   }
13895   return FALSE;
13896 }
13897
13898 int
13899 IntFeature(p, name, loc, cps)
13900      char **p;
13901      char *name;
13902      int *loc;
13903      ChessProgramState *cps;
13904 {
13905   char buf[MSG_SIZ];
13906   int len = strlen(name);
13907   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13908     (*p) += len + 1;
13909     sscanf(*p, "%d", loc);
13910     while (**p && **p != ' ') (*p)++;
13911     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13912     SendToProgram(buf, cps);
13913     return TRUE;
13914   }
13915   return FALSE;
13916 }
13917
13918 int
13919 StringFeature(p, name, loc, cps)
13920      char **p;
13921      char *name;
13922      char loc[];
13923      ChessProgramState *cps;
13924 {
13925   char buf[MSG_SIZ];
13926   int len = strlen(name);
13927   if (strncmp((*p), name, len) == 0
13928       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13929     (*p) += len + 2;
13930     sscanf(*p, "%[^\"]", loc);
13931     while (**p && **p != '\"') (*p)++;
13932     if (**p == '\"') (*p)++;
13933     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13934     SendToProgram(buf, cps);
13935     return TRUE;
13936   }
13937   return FALSE;
13938 }
13939
13940 int
13941 ParseOption(Option *opt, ChessProgramState *cps)
13942 // [HGM] options: process the string that defines an engine option, and determine
13943 // name, type, default value, and allowed value range
13944 {
13945         char *p, *q, buf[MSG_SIZ];
13946         int n, min = (-1)<<31, max = 1<<31, def;
13947
13948         if(p = strstr(opt->name, " -spin ")) {
13949             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13950             if(max < min) max = min; // enforce consistency
13951             if(def < min) def = min;
13952             if(def > max) def = max;
13953             opt->value = def;
13954             opt->min = min;
13955             opt->max = max;
13956             opt->type = Spin;
13957         } else if((p = strstr(opt->name, " -slider "))) {
13958             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13959             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13960             if(max < min) max = min; // enforce consistency
13961             if(def < min) def = min;
13962             if(def > max) def = max;
13963             opt->value = def;
13964             opt->min = min;
13965             opt->max = max;
13966             opt->type = Spin; // Slider;
13967         } else if((p = strstr(opt->name, " -string "))) {
13968             opt->textValue = p+9;
13969             opt->type = TextBox;
13970         } else if((p = strstr(opt->name, " -file "))) {
13971             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13972             opt->textValue = p+7;
13973             opt->type = FileName; // FileName;
13974         } else if((p = strstr(opt->name, " -path "))) {
13975             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13976             opt->textValue = p+7;
13977             opt->type = PathName; // PathName;
13978         } else if(p = strstr(opt->name, " -check ")) {
13979             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13980             opt->value = (def != 0);
13981             opt->type = CheckBox;
13982         } else if(p = strstr(opt->name, " -combo ")) {
13983             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13984             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13985             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13986             opt->value = n = 0;
13987             while(q = StrStr(q, " /// ")) {
13988                 n++; *q = 0;    // count choices, and null-terminate each of them
13989                 q += 5;
13990                 if(*q == '*') { // remember default, which is marked with * prefix
13991                     q++;
13992                     opt->value = n;
13993                 }
13994                 cps->comboList[cps->comboCnt++] = q;
13995             }
13996             cps->comboList[cps->comboCnt++] = NULL;
13997             opt->max = n + 1;
13998             opt->type = ComboBox;
13999         } else if(p = strstr(opt->name, " -button")) {
14000             opt->type = Button;
14001         } else if(p = strstr(opt->name, " -save")) {
14002             opt->type = SaveButton;
14003         } else return FALSE;
14004         *p = 0; // terminate option name
14005         // now look if the command-line options define a setting for this engine option.
14006         if(cps->optionSettings && cps->optionSettings[0])
14007             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14008         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14009           snprintf(buf, MSG_SIZ, "option %s", p);
14010                 if(p = strstr(buf, ",")) *p = 0;
14011                 if(q = strchr(buf, '=')) switch(opt->type) {
14012                     case ComboBox:
14013                         for(n=0; n<opt->max; n++)
14014                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14015                         break;
14016                     case TextBox:
14017                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14018                         break;
14019                     case Spin:
14020                     case CheckBox:
14021                         opt->value = atoi(q+1);
14022                     default:
14023                         break;
14024                 }
14025                 strcat(buf, "\n");
14026                 SendToProgram(buf, cps);
14027         }
14028         return TRUE;
14029 }
14030
14031 void
14032 FeatureDone(cps, val)
14033      ChessProgramState* cps;
14034      int val;
14035 {
14036   DelayedEventCallback cb = GetDelayedEvent();
14037   if ((cb == InitBackEnd3 && cps == &first) ||
14038       (cb == SettingsMenuIfReady && cps == &second) ||
14039       (cb == TwoMachinesEventIfReady && cps == &second)) {
14040     CancelDelayedEvent();
14041     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14042   }
14043   cps->initDone = val;
14044 }
14045
14046 /* Parse feature command from engine */
14047 void
14048 ParseFeatures(args, cps)
14049      char* args;
14050      ChessProgramState *cps;
14051 {
14052   char *p = args;
14053   char *q;
14054   int val;
14055   char buf[MSG_SIZ];
14056
14057   for (;;) {
14058     while (*p == ' ') p++;
14059     if (*p == NULLCHAR) return;
14060
14061     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14062     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14063     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14064     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14065     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14066     if (BoolFeature(&p, "reuse", &val, cps)) {
14067       /* Engine can disable reuse, but can't enable it if user said no */
14068       if (!val) cps->reuse = FALSE;
14069       continue;
14070     }
14071     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14072     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14073       if (gameMode == TwoMachinesPlay) {
14074         DisplayTwoMachinesTitle();
14075       } else {
14076         DisplayTitle("");
14077       }
14078       continue;
14079     }
14080     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14081     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14082     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14083     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14084     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14085     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14086     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14087     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14088     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14089     if (IntFeature(&p, "done", &val, cps)) {
14090       FeatureDone(cps, val);
14091       continue;
14092     }
14093     /* Added by Tord: */
14094     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14095     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14096     /* End of additions by Tord */
14097
14098     /* [HGM] added features: */
14099     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14100     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14101     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14102     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14103     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14104     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14105     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14106         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14107           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14108             SendToProgram(buf, cps);
14109             continue;
14110         }
14111         if(cps->nrOptions >= MAX_OPTIONS) {
14112             cps->nrOptions--;
14113             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14114             DisplayError(buf, 0);
14115         }
14116         continue;
14117     }
14118     /* End of additions by HGM */
14119
14120     /* unknown feature: complain and skip */
14121     q = p;
14122     while (*q && *q != '=') q++;
14123     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14124     SendToProgram(buf, cps);
14125     p = q;
14126     if (*p == '=') {
14127       p++;
14128       if (*p == '\"') {
14129         p++;
14130         while (*p && *p != '\"') p++;
14131         if (*p == '\"') p++;
14132       } else {
14133         while (*p && *p != ' ') p++;
14134       }
14135     }
14136   }
14137
14138 }
14139
14140 void
14141 PeriodicUpdatesEvent(newState)
14142      int newState;
14143 {
14144     if (newState == appData.periodicUpdates)
14145       return;
14146
14147     appData.periodicUpdates=newState;
14148
14149     /* Display type changes, so update it now */
14150 //    DisplayAnalysis();
14151
14152     /* Get the ball rolling again... */
14153     if (newState) {
14154         AnalysisPeriodicEvent(1);
14155         StartAnalysisClock();
14156     }
14157 }
14158
14159 void
14160 PonderNextMoveEvent(newState)
14161      int newState;
14162 {
14163     if (newState == appData.ponderNextMove) return;
14164     if (gameMode == EditPosition) EditPositionDone(TRUE);
14165     if (newState) {
14166         SendToProgram("hard\n", &first);
14167         if (gameMode == TwoMachinesPlay) {
14168             SendToProgram("hard\n", &second);
14169         }
14170     } else {
14171         SendToProgram("easy\n", &first);
14172         thinkOutput[0] = NULLCHAR;
14173         if (gameMode == TwoMachinesPlay) {
14174             SendToProgram("easy\n", &second);
14175         }
14176     }
14177     appData.ponderNextMove = newState;
14178 }
14179
14180 void
14181 NewSettingEvent(option, feature, command, value)
14182      char *command;
14183      int option, value, *feature;
14184 {
14185     char buf[MSG_SIZ];
14186
14187     if (gameMode == EditPosition) EditPositionDone(TRUE);
14188     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14189     if(feature == NULL || *feature) SendToProgram(buf, &first);
14190     if (gameMode == TwoMachinesPlay) {
14191         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14192     }
14193 }
14194
14195 void
14196 ShowThinkingEvent()
14197 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14198 {
14199     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14200     int newState = appData.showThinking
14201         // [HGM] thinking: other features now need thinking output as well
14202         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14203
14204     if (oldState == newState) return;
14205     oldState = newState;
14206     if (gameMode == EditPosition) EditPositionDone(TRUE);
14207     if (oldState) {
14208         SendToProgram("post\n", &first);
14209         if (gameMode == TwoMachinesPlay) {
14210             SendToProgram("post\n", &second);
14211         }
14212     } else {
14213         SendToProgram("nopost\n", &first);
14214         thinkOutput[0] = NULLCHAR;
14215         if (gameMode == TwoMachinesPlay) {
14216             SendToProgram("nopost\n", &second);
14217         }
14218     }
14219 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14220 }
14221
14222 void
14223 AskQuestionEvent(title, question, replyPrefix, which)
14224      char *title; char *question; char *replyPrefix; char *which;
14225 {
14226   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14227   if (pr == NoProc) return;
14228   AskQuestion(title, question, replyPrefix, pr);
14229 }
14230
14231 void
14232 DisplayMove(moveNumber)
14233      int moveNumber;
14234 {
14235     char message[MSG_SIZ];
14236     char res[MSG_SIZ];
14237     char cpThinkOutput[MSG_SIZ];
14238
14239     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14240
14241     if (moveNumber == forwardMostMove - 1 ||
14242         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14243
14244         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14245
14246         if (strchr(cpThinkOutput, '\n')) {
14247             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14248         }
14249     } else {
14250         *cpThinkOutput = NULLCHAR;
14251     }
14252
14253     /* [AS] Hide thinking from human user */
14254     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14255         *cpThinkOutput = NULLCHAR;
14256         if( thinkOutput[0] != NULLCHAR ) {
14257             int i;
14258
14259             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14260                 cpThinkOutput[i] = '.';
14261             }
14262             cpThinkOutput[i] = NULLCHAR;
14263             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14264         }
14265     }
14266
14267     if (moveNumber == forwardMostMove - 1 &&
14268         gameInfo.resultDetails != NULL) {
14269         if (gameInfo.resultDetails[0] == NULLCHAR) {
14270           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14271         } else {
14272           snprintf(res, MSG_SIZ, " {%s} %s",
14273                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14274         }
14275     } else {
14276         res[0] = NULLCHAR;
14277     }
14278
14279     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14280         DisplayMessage(res, cpThinkOutput);
14281     } else {
14282       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14283                 WhiteOnMove(moveNumber) ? " " : ".. ",
14284                 parseList[moveNumber], res);
14285         DisplayMessage(message, cpThinkOutput);
14286     }
14287 }
14288
14289 void
14290 DisplayComment(moveNumber, text)
14291      int moveNumber;
14292      char *text;
14293 {
14294     char title[MSG_SIZ];
14295     char buf[8000]; // comment can be long!
14296     int score, depth;
14297
14298     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14299       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14300     } else {
14301       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14302               WhiteOnMove(moveNumber) ? " " : ".. ",
14303               parseList[moveNumber]);
14304     }
14305     // [HGM] PV info: display PV info together with (or as) comment
14306     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14307       if(text == NULL) text = "";
14308       score = pvInfoList[moveNumber].score;
14309       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14310               depth, (pvInfoList[moveNumber].time+50)/100, text);
14311       text = buf;
14312     }
14313     if (text != NULL && (appData.autoDisplayComment || commentUp))
14314         CommentPopUp(title, text);
14315 }
14316
14317 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14318  * might be busy thinking or pondering.  It can be omitted if your
14319  * gnuchess is configured to stop thinking immediately on any user
14320  * input.  However, that gnuchess feature depends on the FIONREAD
14321  * ioctl, which does not work properly on some flavors of Unix.
14322  */
14323 void
14324 Attention(cps)
14325      ChessProgramState *cps;
14326 {
14327 #if ATTENTION
14328     if (!cps->useSigint) return;
14329     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14330     switch (gameMode) {
14331       case MachinePlaysWhite:
14332       case MachinePlaysBlack:
14333       case TwoMachinesPlay:
14334       case IcsPlayingWhite:
14335       case IcsPlayingBlack:
14336       case AnalyzeMode:
14337       case AnalyzeFile:
14338         /* Skip if we know it isn't thinking */
14339         if (!cps->maybeThinking) return;
14340         if (appData.debugMode)
14341           fprintf(debugFP, "Interrupting %s\n", cps->which);
14342         InterruptChildProcess(cps->pr);
14343         cps->maybeThinking = FALSE;
14344         break;
14345       default:
14346         break;
14347     }
14348 #endif /*ATTENTION*/
14349 }
14350
14351 int
14352 CheckFlags()
14353 {
14354     if (whiteTimeRemaining <= 0) {
14355         if (!whiteFlag) {
14356             whiteFlag = TRUE;
14357             if (appData.icsActive) {
14358                 if (appData.autoCallFlag &&
14359                     gameMode == IcsPlayingBlack && !blackFlag) {
14360                   SendToICS(ics_prefix);
14361                   SendToICS("flag\n");
14362                 }
14363             } else {
14364                 if (blackFlag) {
14365                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14366                 } else {
14367                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14368                     if (appData.autoCallFlag) {
14369                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14370                         return TRUE;
14371                     }
14372                 }
14373             }
14374         }
14375     }
14376     if (blackTimeRemaining <= 0) {
14377         if (!blackFlag) {
14378             blackFlag = TRUE;
14379             if (appData.icsActive) {
14380                 if (appData.autoCallFlag &&
14381                     gameMode == IcsPlayingWhite && !whiteFlag) {
14382                   SendToICS(ics_prefix);
14383                   SendToICS("flag\n");
14384                 }
14385             } else {
14386                 if (whiteFlag) {
14387                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14388                 } else {
14389                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14390                     if (appData.autoCallFlag) {
14391                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14392                         return TRUE;
14393                     }
14394                 }
14395             }
14396         }
14397     }
14398     return FALSE;
14399 }
14400
14401 void
14402 CheckTimeControl()
14403 {
14404     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14405         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14406
14407     /*
14408      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14409      */
14410     if ( !WhiteOnMove(forwardMostMove) ) {
14411         /* White made time control */
14412         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14413         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14414         /* [HGM] time odds: correct new time quota for time odds! */
14415                                             / WhitePlayer()->timeOdds;
14416         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14417     } else {
14418         lastBlack -= blackTimeRemaining;
14419         /* Black made time control */
14420         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14421                                             / WhitePlayer()->other->timeOdds;
14422         lastWhite = whiteTimeRemaining;
14423     }
14424 }
14425
14426 void
14427 DisplayBothClocks()
14428 {
14429     int wom = gameMode == EditPosition ?
14430       !blackPlaysFirst : WhiteOnMove(currentMove);
14431     DisplayWhiteClock(whiteTimeRemaining, wom);
14432     DisplayBlackClock(blackTimeRemaining, !wom);
14433 }
14434
14435
14436 /* Timekeeping seems to be a portability nightmare.  I think everyone
14437    has ftime(), but I'm really not sure, so I'm including some ifdefs
14438    to use other calls if you don't.  Clocks will be less accurate if
14439    you have neither ftime nor gettimeofday.
14440 */
14441
14442 /* VS 2008 requires the #include outside of the function */
14443 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14444 #include <sys/timeb.h>
14445 #endif
14446
14447 /* Get the current time as a TimeMark */
14448 void
14449 GetTimeMark(tm)
14450      TimeMark *tm;
14451 {
14452 #if HAVE_GETTIMEOFDAY
14453
14454     struct timeval timeVal;
14455     struct timezone timeZone;
14456
14457     gettimeofday(&timeVal, &timeZone);
14458     tm->sec = (long) timeVal.tv_sec;
14459     tm->ms = (int) (timeVal.tv_usec / 1000L);
14460
14461 #else /*!HAVE_GETTIMEOFDAY*/
14462 #if HAVE_FTIME
14463
14464 // include <sys/timeb.h> / moved to just above start of function
14465     struct timeb timeB;
14466
14467     ftime(&timeB);
14468     tm->sec = (long) timeB.time;
14469     tm->ms = (int) timeB.millitm;
14470
14471 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14472     tm->sec = (long) time(NULL);
14473     tm->ms = 0;
14474 #endif
14475 #endif
14476 }
14477
14478 /* Return the difference in milliseconds between two
14479    time marks.  We assume the difference will fit in a long!
14480 */
14481 long
14482 SubtractTimeMarks(tm2, tm1)
14483      TimeMark *tm2, *tm1;
14484 {
14485     return 1000L*(tm2->sec - tm1->sec) +
14486            (long) (tm2->ms - tm1->ms);
14487 }
14488
14489
14490 /*
14491  * Code to manage the game clocks.
14492  *
14493  * In tournament play, black starts the clock and then white makes a move.
14494  * We give the human user a slight advantage if he is playing white---the
14495  * clocks don't run until he makes his first move, so it takes zero time.
14496  * Also, we don't account for network lag, so we could get out of sync
14497  * with GNU Chess's clock -- but then, referees are always right.
14498  */
14499
14500 static TimeMark tickStartTM;
14501 static long intendedTickLength;
14502
14503 long
14504 NextTickLength(timeRemaining)
14505      long timeRemaining;
14506 {
14507     long nominalTickLength, nextTickLength;
14508
14509     if (timeRemaining > 0L && timeRemaining <= 10000L)
14510       nominalTickLength = 100L;
14511     else
14512       nominalTickLength = 1000L;
14513     nextTickLength = timeRemaining % nominalTickLength;
14514     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14515
14516     return nextTickLength;
14517 }
14518
14519 /* Adjust clock one minute up or down */
14520 void
14521 AdjustClock(Boolean which, int dir)
14522 {
14523     if(which) blackTimeRemaining += 60000*dir;
14524     else      whiteTimeRemaining += 60000*dir;
14525     DisplayBothClocks();
14526 }
14527
14528 /* Stop clocks and reset to a fresh time control */
14529 void
14530 ResetClocks()
14531 {
14532     (void) StopClockTimer();
14533     if (appData.icsActive) {
14534         whiteTimeRemaining = blackTimeRemaining = 0;
14535     } else if (searchTime) {
14536         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14537         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14538     } else { /* [HGM] correct new time quote for time odds */
14539         whiteTC = blackTC = fullTimeControlString;
14540         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14541         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14542     }
14543     if (whiteFlag || blackFlag) {
14544         DisplayTitle("");
14545         whiteFlag = blackFlag = FALSE;
14546     }
14547     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14548     DisplayBothClocks();
14549 }
14550
14551 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14552
14553 /* Decrement running clock by amount of time that has passed */
14554 void
14555 DecrementClocks()
14556 {
14557     long timeRemaining;
14558     long lastTickLength, fudge;
14559     TimeMark now;
14560
14561     if (!appData.clockMode) return;
14562     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14563
14564     GetTimeMark(&now);
14565
14566     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14567
14568     /* Fudge if we woke up a little too soon */
14569     fudge = intendedTickLength - lastTickLength;
14570     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14571
14572     if (WhiteOnMove(forwardMostMove)) {
14573         if(whiteNPS >= 0) lastTickLength = 0;
14574         timeRemaining = whiteTimeRemaining -= lastTickLength;
14575         if(timeRemaining < 0 && !appData.icsActive) {
14576             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14577             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14578                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14579                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14580             }
14581         }
14582         DisplayWhiteClock(whiteTimeRemaining - fudge,
14583                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14584     } else {
14585         if(blackNPS >= 0) lastTickLength = 0;
14586         timeRemaining = blackTimeRemaining -= lastTickLength;
14587         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14588             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14589             if(suddenDeath) {
14590                 blackStartMove = forwardMostMove;
14591                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14592             }
14593         }
14594         DisplayBlackClock(blackTimeRemaining - fudge,
14595                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14596     }
14597     if (CheckFlags()) return;
14598
14599     tickStartTM = now;
14600     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14601     StartClockTimer(intendedTickLength);
14602
14603     /* if the time remaining has fallen below the alarm threshold, sound the
14604      * alarm. if the alarm has sounded and (due to a takeback or time control
14605      * with increment) the time remaining has increased to a level above the
14606      * threshold, reset the alarm so it can sound again.
14607      */
14608
14609     if (appData.icsActive && appData.icsAlarm) {
14610
14611         /* make sure we are dealing with the user's clock */
14612         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14613                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14614            )) return;
14615
14616         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14617             alarmSounded = FALSE;
14618         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14619             PlayAlarmSound();
14620             alarmSounded = TRUE;
14621         }
14622     }
14623 }
14624
14625
14626 /* A player has just moved, so stop the previously running
14627    clock and (if in clock mode) start the other one.
14628    We redisplay both clocks in case we're in ICS mode, because
14629    ICS gives us an update to both clocks after every move.
14630    Note that this routine is called *after* forwardMostMove
14631    is updated, so the last fractional tick must be subtracted
14632    from the color that is *not* on move now.
14633 */
14634 void
14635 SwitchClocks(int newMoveNr)
14636 {
14637     long lastTickLength;
14638     TimeMark now;
14639     int flagged = FALSE;
14640
14641     GetTimeMark(&now);
14642
14643     if (StopClockTimer() && appData.clockMode) {
14644         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14645         if (!WhiteOnMove(forwardMostMove)) {
14646             if(blackNPS >= 0) lastTickLength = 0;
14647             blackTimeRemaining -= lastTickLength;
14648            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14649 //         if(pvInfoList[forwardMostMove].time == -1)
14650                  pvInfoList[forwardMostMove].time =               // use GUI time
14651                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14652         } else {
14653            if(whiteNPS >= 0) lastTickLength = 0;
14654            whiteTimeRemaining -= lastTickLength;
14655            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14656 //         if(pvInfoList[forwardMostMove].time == -1)
14657                  pvInfoList[forwardMostMove].time =
14658                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14659         }
14660         flagged = CheckFlags();
14661     }
14662     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14663     CheckTimeControl();
14664
14665     if (flagged || !appData.clockMode) return;
14666
14667     switch (gameMode) {
14668       case MachinePlaysBlack:
14669       case MachinePlaysWhite:
14670       case BeginningOfGame:
14671         if (pausing) return;
14672         break;
14673
14674       case EditGame:
14675       case PlayFromGameFile:
14676       case IcsExamining:
14677         return;
14678
14679       default:
14680         break;
14681     }
14682
14683     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14684         if(WhiteOnMove(forwardMostMove))
14685              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14686         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14687     }
14688
14689     tickStartTM = now;
14690     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14691       whiteTimeRemaining : blackTimeRemaining);
14692     StartClockTimer(intendedTickLength);
14693 }
14694
14695
14696 /* Stop both clocks */
14697 void
14698 StopClocks()
14699 {
14700     long lastTickLength;
14701     TimeMark now;
14702
14703     if (!StopClockTimer()) return;
14704     if (!appData.clockMode) return;
14705
14706     GetTimeMark(&now);
14707
14708     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14709     if (WhiteOnMove(forwardMostMove)) {
14710         if(whiteNPS >= 0) lastTickLength = 0;
14711         whiteTimeRemaining -= lastTickLength;
14712         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14713     } else {
14714         if(blackNPS >= 0) lastTickLength = 0;
14715         blackTimeRemaining -= lastTickLength;
14716         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14717     }
14718     CheckFlags();
14719 }
14720
14721 /* Start clock of player on move.  Time may have been reset, so
14722    if clock is already running, stop and restart it. */
14723 void
14724 StartClocks()
14725 {
14726     (void) StopClockTimer(); /* in case it was running already */
14727     DisplayBothClocks();
14728     if (CheckFlags()) return;
14729
14730     if (!appData.clockMode) return;
14731     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14732
14733     GetTimeMark(&tickStartTM);
14734     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14735       whiteTimeRemaining : blackTimeRemaining);
14736
14737    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14738     whiteNPS = blackNPS = -1;
14739     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14740        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14741         whiteNPS = first.nps;
14742     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14743        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14744         blackNPS = first.nps;
14745     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14746         whiteNPS = second.nps;
14747     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14748         blackNPS = second.nps;
14749     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14750
14751     StartClockTimer(intendedTickLength);
14752 }
14753
14754 char *
14755 TimeString(ms)
14756      long ms;
14757 {
14758     long second, minute, hour, day;
14759     char *sign = "";
14760     static char buf[32];
14761
14762     if (ms > 0 && ms <= 9900) {
14763       /* convert milliseconds to tenths, rounding up */
14764       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14765
14766       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14767       return buf;
14768     }
14769
14770     /* convert milliseconds to seconds, rounding up */
14771     /* use floating point to avoid strangeness of integer division
14772        with negative dividends on many machines */
14773     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14774
14775     if (second < 0) {
14776         sign = "-";
14777         second = -second;
14778     }
14779
14780     day = second / (60 * 60 * 24);
14781     second = second % (60 * 60 * 24);
14782     hour = second / (60 * 60);
14783     second = second % (60 * 60);
14784     minute = second / 60;
14785     second = second % 60;
14786
14787     if (day > 0)
14788       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14789               sign, day, hour, minute, second);
14790     else if (hour > 0)
14791       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14792     else
14793       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14794
14795     return buf;
14796 }
14797
14798
14799 /*
14800  * This is necessary because some C libraries aren't ANSI C compliant yet.
14801  */
14802 char *
14803 StrStr(string, match)
14804      char *string, *match;
14805 {
14806     int i, length;
14807
14808     length = strlen(match);
14809
14810     for (i = strlen(string) - length; i >= 0; i--, string++)
14811       if (!strncmp(match, string, length))
14812         return string;
14813
14814     return NULL;
14815 }
14816
14817 char *
14818 StrCaseStr(string, match)
14819      char *string, *match;
14820 {
14821     int i, j, length;
14822
14823     length = strlen(match);
14824
14825     for (i = strlen(string) - length; i >= 0; i--, string++) {
14826         for (j = 0; j < length; j++) {
14827             if (ToLower(match[j]) != ToLower(string[j]))
14828               break;
14829         }
14830         if (j == length) return string;
14831     }
14832
14833     return NULL;
14834 }
14835
14836 #ifndef _amigados
14837 int
14838 StrCaseCmp(s1, s2)
14839      char *s1, *s2;
14840 {
14841     char c1, c2;
14842
14843     for (;;) {
14844         c1 = ToLower(*s1++);
14845         c2 = ToLower(*s2++);
14846         if (c1 > c2) return 1;
14847         if (c1 < c2) return -1;
14848         if (c1 == NULLCHAR) return 0;
14849     }
14850 }
14851
14852
14853 int
14854 ToLower(c)
14855      int c;
14856 {
14857     return isupper(c) ? tolower(c) : c;
14858 }
14859
14860
14861 int
14862 ToUpper(c)
14863      int c;
14864 {
14865     return islower(c) ? toupper(c) : c;
14866 }
14867 #endif /* !_amigados    */
14868
14869 char *
14870 StrSave(s)
14871      char *s;
14872 {
14873   char *ret;
14874
14875   if ((ret = (char *) malloc(strlen(s) + 1)))
14876     {
14877       safeStrCpy(ret, s, strlen(s)+1);
14878     }
14879   return ret;
14880 }
14881
14882 char *
14883 StrSavePtr(s, savePtr)
14884      char *s, **savePtr;
14885 {
14886     if (*savePtr) {
14887         free(*savePtr);
14888     }
14889     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14890       safeStrCpy(*savePtr, s, strlen(s)+1);
14891     }
14892     return(*savePtr);
14893 }
14894
14895 char *
14896 PGNDate()
14897 {
14898     time_t clock;
14899     struct tm *tm;
14900     char buf[MSG_SIZ];
14901
14902     clock = time((time_t *)NULL);
14903     tm = localtime(&clock);
14904     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14905             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14906     return StrSave(buf);
14907 }
14908
14909
14910 char *
14911 PositionToFEN(move, overrideCastling)
14912      int move;
14913      char *overrideCastling;
14914 {
14915     int i, j, fromX, fromY, toX, toY;
14916     int whiteToPlay;
14917     char buf[128];
14918     char *p, *q;
14919     int emptycount;
14920     ChessSquare piece;
14921
14922     whiteToPlay = (gameMode == EditPosition) ?
14923       !blackPlaysFirst : (move % 2 == 0);
14924     p = buf;
14925
14926     /* Piece placement data */
14927     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14928         emptycount = 0;
14929         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14930             if (boards[move][i][j] == EmptySquare) {
14931                 emptycount++;
14932             } else { ChessSquare piece = boards[move][i][j];
14933                 if (emptycount > 0) {
14934                     if(emptycount<10) /* [HGM] can be >= 10 */
14935                         *p++ = '0' + emptycount;
14936                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14937                     emptycount = 0;
14938                 }
14939                 if(PieceToChar(piece) == '+') {
14940                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14941                     *p++ = '+';
14942                     piece = (ChessSquare)(DEMOTED piece);
14943                 }
14944                 *p++ = PieceToChar(piece);
14945                 if(p[-1] == '~') {
14946                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14947                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14948                     *p++ = '~';
14949                 }
14950             }
14951         }
14952         if (emptycount > 0) {
14953             if(emptycount<10) /* [HGM] can be >= 10 */
14954                 *p++ = '0' + emptycount;
14955             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14956             emptycount = 0;
14957         }
14958         *p++ = '/';
14959     }
14960     *(p - 1) = ' ';
14961
14962     /* [HGM] print Crazyhouse or Shogi holdings */
14963     if( gameInfo.holdingsWidth ) {
14964         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14965         q = p;
14966         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14967             piece = boards[move][i][BOARD_WIDTH-1];
14968             if( piece != EmptySquare )
14969               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14970                   *p++ = PieceToChar(piece);
14971         }
14972         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14973             piece = boards[move][BOARD_HEIGHT-i-1][0];
14974             if( piece != EmptySquare )
14975               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14976                   *p++ = PieceToChar(piece);
14977         }
14978
14979         if( q == p ) *p++ = '-';
14980         *p++ = ']';
14981         *p++ = ' ';
14982     }
14983
14984     /* Active color */
14985     *p++ = whiteToPlay ? 'w' : 'b';
14986     *p++ = ' ';
14987
14988   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14989     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14990   } else {
14991   if(nrCastlingRights) {
14992      q = p;
14993      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14994        /* [HGM] write directly from rights */
14995            if(boards[move][CASTLING][2] != NoRights &&
14996               boards[move][CASTLING][0] != NoRights   )
14997                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14998            if(boards[move][CASTLING][2] != NoRights &&
14999               boards[move][CASTLING][1] != NoRights   )
15000                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15001            if(boards[move][CASTLING][5] != NoRights &&
15002               boards[move][CASTLING][3] != NoRights   )
15003                 *p++ = boards[move][CASTLING][3] + AAA;
15004            if(boards[move][CASTLING][5] != NoRights &&
15005               boards[move][CASTLING][4] != NoRights   )
15006                 *p++ = boards[move][CASTLING][4] + AAA;
15007      } else {
15008
15009         /* [HGM] write true castling rights */
15010         if( nrCastlingRights == 6 ) {
15011             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15012                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15013             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15014                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15015             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15016                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15017             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15018                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15019         }
15020      }
15021      if (q == p) *p++ = '-'; /* No castling rights */
15022      *p++ = ' ';
15023   }
15024
15025   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15026      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15027     /* En passant target square */
15028     if (move > backwardMostMove) {
15029         fromX = moveList[move - 1][0] - AAA;
15030         fromY = moveList[move - 1][1] - ONE;
15031         toX = moveList[move - 1][2] - AAA;
15032         toY = moveList[move - 1][3] - ONE;
15033         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15034             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15035             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15036             fromX == toX) {
15037             /* 2-square pawn move just happened */
15038             *p++ = toX + AAA;
15039             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15040         } else {
15041             *p++ = '-';
15042         }
15043     } else if(move == backwardMostMove) {
15044         // [HGM] perhaps we should always do it like this, and forget the above?
15045         if((signed char)boards[move][EP_STATUS] >= 0) {
15046             *p++ = boards[move][EP_STATUS] + AAA;
15047             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15048         } else {
15049             *p++ = '-';
15050         }
15051     } else {
15052         *p++ = '-';
15053     }
15054     *p++ = ' ';
15055   }
15056   }
15057
15058     /* [HGM] find reversible plies */
15059     {   int i = 0, j=move;
15060
15061         if (appData.debugMode) { int k;
15062             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15063             for(k=backwardMostMove; k<=forwardMostMove; k++)
15064                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15065
15066         }
15067
15068         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15069         if( j == backwardMostMove ) i += initialRulePlies;
15070         sprintf(p, "%d ", i);
15071         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15072     }
15073     /* Fullmove number */
15074     sprintf(p, "%d", (move / 2) + 1);
15075
15076     return StrSave(buf);
15077 }
15078
15079 Boolean
15080 ParseFEN(board, blackPlaysFirst, fen)
15081     Board board;
15082      int *blackPlaysFirst;
15083      char *fen;
15084 {
15085     int i, j;
15086     char *p, c;
15087     int emptycount;
15088     ChessSquare piece;
15089
15090     p = fen;
15091
15092     /* [HGM] by default clear Crazyhouse holdings, if present */
15093     if(gameInfo.holdingsWidth) {
15094        for(i=0; i<BOARD_HEIGHT; i++) {
15095            board[i][0]             = EmptySquare; /* black holdings */
15096            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15097            board[i][1]             = (ChessSquare) 0; /* black counts */
15098            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15099        }
15100     }
15101
15102     /* Piece placement data */
15103     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15104         j = 0;
15105         for (;;) {
15106             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15107                 if (*p == '/') p++;
15108                 emptycount = gameInfo.boardWidth - j;
15109                 while (emptycount--)
15110                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15111                 break;
15112 #if(BOARD_FILES >= 10)
15113             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15114                 p++; emptycount=10;
15115                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15116                 while (emptycount--)
15117                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15118 #endif
15119             } else if (isdigit(*p)) {
15120                 emptycount = *p++ - '0';
15121                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15122                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15123                 while (emptycount--)
15124                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15125             } else if (*p == '+' || isalpha(*p)) {
15126                 if (j >= gameInfo.boardWidth) return FALSE;
15127                 if(*p=='+') {
15128                     piece = CharToPiece(*++p);
15129                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15130                     piece = (ChessSquare) (PROMOTED piece ); p++;
15131                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15132                 } else piece = CharToPiece(*p++);
15133
15134                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15135                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15136                     piece = (ChessSquare) (PROMOTED piece);
15137                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15138                     p++;
15139                 }
15140                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15141             } else {
15142                 return FALSE;
15143             }
15144         }
15145     }
15146     while (*p == '/' || *p == ' ') p++;
15147
15148     /* [HGM] look for Crazyhouse holdings here */
15149     while(*p==' ') p++;
15150     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15151         if(*p == '[') p++;
15152         if(*p == '-' ) p++; /* empty holdings */ else {
15153             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15154             /* if we would allow FEN reading to set board size, we would   */
15155             /* have to add holdings and shift the board read so far here   */
15156             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15157                 p++;
15158                 if((int) piece >= (int) BlackPawn ) {
15159                     i = (int)piece - (int)BlackPawn;
15160                     i = PieceToNumber((ChessSquare)i);
15161                     if( i >= gameInfo.holdingsSize ) return FALSE;
15162                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15163                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15164                 } else {
15165                     i = (int)piece - (int)WhitePawn;
15166                     i = PieceToNumber((ChessSquare)i);
15167                     if( i >= gameInfo.holdingsSize ) return FALSE;
15168                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15169                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15170                 }
15171             }
15172         }
15173         if(*p == ']') p++;
15174     }
15175
15176     while(*p == ' ') p++;
15177
15178     /* Active color */
15179     c = *p++;
15180     if(appData.colorNickNames) {
15181       if( c == appData.colorNickNames[0] ) c = 'w'; else
15182       if( c == appData.colorNickNames[1] ) c = 'b';
15183     }
15184     switch (c) {
15185       case 'w':
15186         *blackPlaysFirst = FALSE;
15187         break;
15188       case 'b':
15189         *blackPlaysFirst = TRUE;
15190         break;
15191       default:
15192         return FALSE;
15193     }
15194
15195     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15196     /* return the extra info in global variiables             */
15197
15198     /* set defaults in case FEN is incomplete */
15199     board[EP_STATUS] = EP_UNKNOWN;
15200     for(i=0; i<nrCastlingRights; i++ ) {
15201         board[CASTLING][i] =
15202             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15203     }   /* assume possible unless obviously impossible */
15204     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15205     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15206     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15207                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15208     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15209     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15210     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15211                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15212     FENrulePlies = 0;
15213
15214     while(*p==' ') p++;
15215     if(nrCastlingRights) {
15216       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15217           /* castling indicator present, so default becomes no castlings */
15218           for(i=0; i<nrCastlingRights; i++ ) {
15219                  board[CASTLING][i] = NoRights;
15220           }
15221       }
15222       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15223              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15224              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15225              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15226         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15227
15228         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15229             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15230             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15231         }
15232         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15233             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15234         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15235                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15236         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15237                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15238         switch(c) {
15239           case'K':
15240               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15241               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15242               board[CASTLING][2] = whiteKingFile;
15243               break;
15244           case'Q':
15245               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15246               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15247               board[CASTLING][2] = whiteKingFile;
15248               break;
15249           case'k':
15250               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15251               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15252               board[CASTLING][5] = blackKingFile;
15253               break;
15254           case'q':
15255               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15256               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15257               board[CASTLING][5] = blackKingFile;
15258           case '-':
15259               break;
15260           default: /* FRC castlings */
15261               if(c >= 'a') { /* black rights */
15262                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15263                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15264                   if(i == BOARD_RGHT) break;
15265                   board[CASTLING][5] = i;
15266                   c -= AAA;
15267                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15268                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15269                   if(c > i)
15270                       board[CASTLING][3] = c;
15271                   else
15272                       board[CASTLING][4] = c;
15273               } else { /* white rights */
15274                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15275                     if(board[0][i] == WhiteKing) break;
15276                   if(i == BOARD_RGHT) break;
15277                   board[CASTLING][2] = i;
15278                   c -= AAA - 'a' + 'A';
15279                   if(board[0][c] >= WhiteKing) break;
15280                   if(c > i)
15281                       board[CASTLING][0] = c;
15282                   else
15283                       board[CASTLING][1] = c;
15284               }
15285         }
15286       }
15287       for(i=0; i<nrCastlingRights; i++)
15288         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15289     if (appData.debugMode) {
15290         fprintf(debugFP, "FEN castling rights:");
15291         for(i=0; i<nrCastlingRights; i++)
15292         fprintf(debugFP, " %d", board[CASTLING][i]);
15293         fprintf(debugFP, "\n");
15294     }
15295
15296       while(*p==' ') p++;
15297     }
15298
15299     /* read e.p. field in games that know e.p. capture */
15300     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15301        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15302       if(*p=='-') {
15303         p++; board[EP_STATUS] = EP_NONE;
15304       } else {
15305          char c = *p++ - AAA;
15306
15307          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15308          if(*p >= '0' && *p <='9') p++;
15309          board[EP_STATUS] = c;
15310       }
15311     }
15312
15313
15314     if(sscanf(p, "%d", &i) == 1) {
15315         FENrulePlies = i; /* 50-move ply counter */
15316         /* (The move number is still ignored)    */
15317     }
15318
15319     return TRUE;
15320 }
15321
15322 void
15323 EditPositionPasteFEN(char *fen)
15324 {
15325   if (fen != NULL) {
15326     Board initial_position;
15327
15328     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15329       DisplayError(_("Bad FEN position in clipboard"), 0);
15330       return ;
15331     } else {
15332       int savedBlackPlaysFirst = blackPlaysFirst;
15333       EditPositionEvent();
15334       blackPlaysFirst = savedBlackPlaysFirst;
15335       CopyBoard(boards[0], initial_position);
15336       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15337       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15338       DisplayBothClocks();
15339       DrawPosition(FALSE, boards[currentMove]);
15340     }
15341   }
15342 }
15343
15344 static char cseq[12] = "\\   ";
15345
15346 Boolean set_cont_sequence(char *new_seq)
15347 {
15348     int len;
15349     Boolean ret;
15350
15351     // handle bad attempts to set the sequence
15352         if (!new_seq)
15353                 return 0; // acceptable error - no debug
15354
15355     len = strlen(new_seq);
15356     ret = (len > 0) && (len < sizeof(cseq));
15357     if (ret)
15358       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15359     else if (appData.debugMode)
15360       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15361     return ret;
15362 }
15363
15364 /*
15365     reformat a source message so words don't cross the width boundary.  internal
15366     newlines are not removed.  returns the wrapped size (no null character unless
15367     included in source message).  If dest is NULL, only calculate the size required
15368     for the dest buffer.  lp argument indicats line position upon entry, and it's
15369     passed back upon exit.
15370 */
15371 int wrap(char *dest, char *src, int count, int width, int *lp)
15372 {
15373     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15374
15375     cseq_len = strlen(cseq);
15376     old_line = line = *lp;
15377     ansi = len = clen = 0;
15378
15379     for (i=0; i < count; i++)
15380     {
15381         if (src[i] == '\033')
15382             ansi = 1;
15383
15384         // if we hit the width, back up
15385         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15386         {
15387             // store i & len in case the word is too long
15388             old_i = i, old_len = len;
15389
15390             // find the end of the last word
15391             while (i && src[i] != ' ' && src[i] != '\n')
15392             {
15393                 i--;
15394                 len--;
15395             }
15396
15397             // word too long?  restore i & len before splitting it
15398             if ((old_i-i+clen) >= width)
15399             {
15400                 i = old_i;
15401                 len = old_len;
15402             }
15403
15404             // extra space?
15405             if (i && src[i-1] == ' ')
15406                 len--;
15407
15408             if (src[i] != ' ' && src[i] != '\n')
15409             {
15410                 i--;
15411                 if (len)
15412                     len--;
15413             }
15414
15415             // now append the newline and continuation sequence
15416             if (dest)
15417                 dest[len] = '\n';
15418             len++;
15419             if (dest)
15420                 strncpy(dest+len, cseq, cseq_len);
15421             len += cseq_len;
15422             line = cseq_len;
15423             clen = cseq_len;
15424             continue;
15425         }
15426
15427         if (dest)
15428             dest[len] = src[i];
15429         len++;
15430         if (!ansi)
15431             line++;
15432         if (src[i] == '\n')
15433             line = 0;
15434         if (src[i] == 'm')
15435             ansi = 0;
15436     }
15437     if (dest && appData.debugMode)
15438     {
15439         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15440             count, width, line, len, *lp);
15441         show_bytes(debugFP, src, count);
15442         fprintf(debugFP, "\ndest: ");
15443         show_bytes(debugFP, dest, len);
15444         fprintf(debugFP, "\n");
15445     }
15446     *lp = dest ? line : old_line;
15447
15448     return len;
15449 }
15450
15451 // [HGM] vari: routines for shelving variations
15452
15453 void
15454 PushTail(int firstMove, int lastMove)
15455 {
15456         int i, j, nrMoves = lastMove - firstMove;
15457
15458         if(appData.icsActive) { // only in local mode
15459                 forwardMostMove = currentMove; // mimic old ICS behavior
15460                 return;
15461         }
15462         if(storedGames >= MAX_VARIATIONS-1) return;
15463
15464         // push current tail of game on stack
15465         savedResult[storedGames] = gameInfo.result;
15466         savedDetails[storedGames] = gameInfo.resultDetails;
15467         gameInfo.resultDetails = NULL;
15468         savedFirst[storedGames] = firstMove;
15469         savedLast [storedGames] = lastMove;
15470         savedFramePtr[storedGames] = framePtr;
15471         framePtr -= nrMoves; // reserve space for the boards
15472         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15473             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15474             for(j=0; j<MOVE_LEN; j++)
15475                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15476             for(j=0; j<2*MOVE_LEN; j++)
15477                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15478             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15479             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15480             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15481             pvInfoList[firstMove+i-1].depth = 0;
15482             commentList[framePtr+i] = commentList[firstMove+i];
15483             commentList[firstMove+i] = NULL;
15484         }
15485
15486         storedGames++;
15487         forwardMostMove = firstMove; // truncate game so we can start variation
15488         if(storedGames == 1) GreyRevert(FALSE);
15489 }
15490
15491 Boolean
15492 PopTail(Boolean annotate)
15493 {
15494         int i, j, nrMoves;
15495         char buf[8000], moveBuf[20];
15496
15497         if(appData.icsActive) return FALSE; // only in local mode
15498         if(!storedGames) return FALSE; // sanity
15499         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15500
15501         storedGames--;
15502         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15503         nrMoves = savedLast[storedGames] - currentMove;
15504         if(annotate) {
15505                 int cnt = 10;
15506                 if(!WhiteOnMove(currentMove))
15507                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15508                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15509                 for(i=currentMove; i<forwardMostMove; i++) {
15510                         if(WhiteOnMove(i))
15511                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15512                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15513                         strcat(buf, moveBuf);
15514                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15515                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15516                 }
15517                 strcat(buf, ")");
15518         }
15519         for(i=1; i<=nrMoves; i++) { // copy last variation back
15520             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15521             for(j=0; j<MOVE_LEN; j++)
15522                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15523             for(j=0; j<2*MOVE_LEN; j++)
15524                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15525             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15526             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15527             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15528             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15529             commentList[currentMove+i] = commentList[framePtr+i];
15530             commentList[framePtr+i] = NULL;
15531         }
15532         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15533         framePtr = savedFramePtr[storedGames];
15534         gameInfo.result = savedResult[storedGames];
15535         if(gameInfo.resultDetails != NULL) {
15536             free(gameInfo.resultDetails);
15537       }
15538         gameInfo.resultDetails = savedDetails[storedGames];
15539         forwardMostMove = currentMove + nrMoves;
15540         if(storedGames == 0) GreyRevert(TRUE);
15541         return TRUE;
15542 }
15543
15544 void
15545 CleanupTail()
15546 {       // remove all shelved variations
15547         int i;
15548         for(i=0; i<storedGames; i++) {
15549             if(savedDetails[i])
15550                 free(savedDetails[i]);
15551             savedDetails[i] = NULL;
15552         }
15553         for(i=framePtr; i<MAX_MOVES; i++) {
15554                 if(commentList[i]) free(commentList[i]);
15555                 commentList[i] = NULL;
15556         }
15557         framePtr = MAX_MOVES-1;
15558         storedGames = 0;
15559 }
15560
15561 void
15562 LoadVariation(int index, char *text)
15563 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15564         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15565         int level = 0, move;
15566
15567         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15568         // first find outermost bracketing variation
15569         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15570             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15571                 if(*p == '{') wait = '}'; else
15572                 if(*p == '[') wait = ']'; else
15573                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15574                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15575             }
15576             if(*p == wait) wait = NULLCHAR; // closing ]} found
15577             p++;
15578         }
15579         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15580         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15581         end[1] = NULLCHAR; // clip off comment beyond variation
15582         ToNrEvent(currentMove-1);
15583         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15584         // kludge: use ParsePV() to append variation to game
15585         move = currentMove;
15586         ParsePV(start, TRUE);
15587         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15588         ClearPremoveHighlights();
15589         CommentPopDown();
15590         ToNrEvent(currentMove+1);
15591 }
15592