938e27fc04263b892318c18109cad571052f1d12
[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 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
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 { // [HGM] made safe
315   int i;
316   assert( dst != NULL );
317   assert( src != NULL );
318   assert( count > 0 );
319
320   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321   if(  i == count && dst[count-1] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
326     }
327
328   return dst;
329 }
330
331 /* Some compiler can't cast u64 to double
332  * This function do the job for us:
333
334  * We use the highest bit for cast, this only
335  * works if the highest bit is not
336  * in use (This should not happen)
337  *
338  * We used this for all compiler
339  */
340 double
341 u64ToDouble(u64 value)
342 {
343   double r;
344   u64 tmp = value & u64Const(0x7fffffffffffffff);
345   r = (double)(s64)tmp;
346   if (value & u64Const(0x8000000000000000))
347        r +=  9.2233720368547758080e18; /* 2^63 */
348  return r;
349 }
350
351 /* Fake up flags for now, as we aren't keeping track of castling
352    availability yet. [HGM] Change of logic: the flag now only
353    indicates the type of castlings allowed by the rule of the game.
354    The actual rights themselves are maintained in the array
355    castlingRights, as part of the game history, and are not probed
356    by this function.
357  */
358 int
359 PosFlags(index)
360 {
361   int flags = F_ALL_CASTLE_OK;
362   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363   switch (gameInfo.variant) {
364   case VariantSuicide:
365     flags &= ~F_ALL_CASTLE_OK;
366   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367     flags |= F_IGNORE_CHECK;
368   case VariantLosers:
369     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
370     break;
371   case VariantAtomic:
372     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
373     break;
374   case VariantKriegspiel:
375     flags |= F_KRIEGSPIEL_CAPTURE;
376     break;
377   case VariantCapaRandom:
378   case VariantFischeRandom:
379     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380   case VariantNoCastle:
381   case VariantShatranj:
382   case VariantCourier:
383   case VariantMakruk:
384     flags &= ~F_ALL_CASTLE_OK;
385     break;
386   default:
387     break;
388   }
389   return flags;
390 }
391
392 FILE *gameFileFP, *debugFP;
393
394 /*
395     [AS] Note: sometimes, the sscanf() function is used to parse the input
396     into a fixed-size buffer. Because of this, we must be prepared to
397     receive strings as long as the size of the input buffer, which is currently
398     set to 4K for Windows and 8K for the rest.
399     So, we must either allocate sufficiently large buffers here, or
400     reduce the size of the input buffer in the input reading part.
401 */
402
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
406
407 ChessProgramState first, second;
408
409 /* premove variables */
410 int premoveToX = 0;
411 int premoveToY = 0;
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
415 int gotPremove = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
418
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
421
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
449
450 int have_sent_ICS_logon = 0;
451 int movesPerSession;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
457 int matchGame = 0;
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
461
462 /* animateTraining preserves the state of appData.animate
463  * when Training mode is activated. This allows the
464  * response to be animated when appData.animate == TRUE and
465  * appData.animateDragging == TRUE.
466  */
467 Boolean animateTraining;
468
469 GameInfo gameInfo;
470
471 AppData appData;
472
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char  initialRights[BOARD_FILES];
477 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int   initialRulePlies, FENrulePlies;
479 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
480 int loadFlag = 0;
481 int shuffleOpenings;
482 int mute; // mute all sounds
483
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
487 int storedGames = 0;
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
493
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
497
498 ChessSquare  FIDEArray[2][BOARD_FILES] = {
499     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502         BlackKing, BlackBishop, BlackKnight, BlackRook }
503 };
504
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackKing, BlackKnight, BlackRook }
510 };
511
512 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515     { BlackRook, BlackMan, BlackBishop, BlackQueen,
516         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
517 };
518
519 ChessSquare SpartanArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
523         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964       case VariantSChess:     /* S-Chess, should work */
965       case VariantSpartan:    /* should work */
966         break;
967       }
968     }
969
970     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
971     InitEngineUCI( installDir, &second );
972 }
973
974 int NextIntegerFromString( char ** str, long * value )
975 {
976     int result = -1;
977     char * s = *str;
978
979     while( *s == ' ' || *s == '\t' ) {
980         s++;
981     }
982
983     *value = 0;
984
985     if( *s >= '0' && *s <= '9' ) {
986         while( *s >= '0' && *s <= '9' ) {
987             *value = *value * 10 + (*s - '0');
988             s++;
989         }
990
991         result = 0;
992     }
993
994     *str = s;
995
996     return result;
997 }
998
999 int NextTimeControlFromString( char ** str, long * value )
1000 {
1001     long temp;
1002     int result = NextIntegerFromString( str, &temp );
1003
1004     if( result == 0 ) {
1005         *value = temp * 60; /* Minutes */
1006         if( **str == ':' ) {
1007             (*str)++;
1008             result = NextIntegerFromString( str, &temp );
1009             *value += temp; /* Seconds */
1010         }
1011     }
1012
1013     return result;
1014 }
1015
1016 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1017 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1018     int result = -1, type = 0; long temp, temp2;
1019
1020     if(**str != ':') return -1; // old params remain in force!
1021     (*str)++;
1022     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1023     if( NextIntegerFromString( str, &temp ) ) return -1;
1024     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1025
1026     if(**str != '/') {
1027         /* time only: incremental or sudden-death time control */
1028         if(**str == '+') { /* increment follows; read it */
1029             (*str)++;
1030             if(**str == '!') type = *(*str)++; // Bronstein TC
1031             if(result = NextIntegerFromString( str, &temp2)) return -1;
1032             *inc = temp2 * 1000;
1033             if(**str == '.') { // read fraction of increment
1034                 char *start = ++(*str);
1035                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1036                 temp2 *= 1000;
1037                 while(start++ < *str) temp2 /= 10;
1038                 *inc += temp2;
1039             }
1040         } else *inc = 0;
1041         *moves = 0; *tc = temp * 1000; *incType = type;
1042         return 0;
1043     }
1044
1045     (*str)++; /* classical time control */
1046     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1047
1048     if(result == 0) {
1049         *moves = temp;
1050         *tc    = temp2 * 1000;
1051         *inc   = 0;
1052         *incType = type;
1053     }
1054     return result;
1055 }
1056
1057 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1058 {   /* [HGM] get time to add from the multi-session time-control string */
1059     int incType, moves=1; /* kludge to force reading of first session */
1060     long time, increment;
1061     char *s = tcString;
1062
1063     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1064     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1065     do {
1066         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1067         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1068         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1069         if(movenr == -1) return time;    /* last move before new session     */
1070         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1071         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1072         if(!moves) return increment;     /* current session is incremental   */
1073         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1074     } while(movenr >= -1);               /* try again for next session       */
1075
1076     return 0; // no new time quota on this move
1077 }
1078
1079 int
1080 ParseTimeControl(tc, ti, mps)
1081      char *tc;
1082      float ti;
1083      int mps;
1084 {
1085   long tc1;
1086   long tc2;
1087   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1088   int min, sec=0;
1089
1090   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1091   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1092       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1093   if(ti > 0) {
1094
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1097     else 
1098       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1099   } else {
1100     if(mps)
1101       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1102     else 
1103       snprintf(buf, MSG_SIZ, ":%s", mytc);
1104   }
1105   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1106   
1107   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1108     return FALSE;
1109   }
1110
1111   if( *tc == '/' ) {
1112     /* Parse second time control */
1113     tc++;
1114
1115     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1116       return FALSE;
1117     }
1118
1119     if( tc2 == 0 ) {
1120       return FALSE;
1121     }
1122
1123     timeControl_2 = tc2 * 1000;
1124   }
1125   else {
1126     timeControl_2 = 0;
1127   }
1128
1129   if( tc1 == 0 ) {
1130     return FALSE;
1131   }
1132
1133   timeControl = tc1 * 1000;
1134
1135   if (ti >= 0) {
1136     timeIncrement = ti * 1000;  /* convert to ms */
1137     movesPerSession = 0;
1138   } else {
1139     timeIncrement = 0;
1140     movesPerSession = mps;
1141   }
1142   return TRUE;
1143 }
1144
1145 void
1146 InitBackEnd2()
1147 {
1148     if (appData.debugMode) {
1149         fprintf(debugFP, "%s\n", programVersion);
1150     }
1151
1152     set_cont_sequence(appData.wrapContSeq);
1153     if (appData.matchGames > 0) {
1154         appData.matchMode = TRUE;
1155     } else if (appData.matchMode) {
1156         appData.matchGames = 1;
1157     }
1158     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1159         appData.matchGames = appData.sameColorGames;
1160     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1161         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1162         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1163     }
1164     Reset(TRUE, FALSE);
1165     if (appData.noChessProgram || first.protocolVersion == 1) {
1166       InitBackEnd3();
1167     } else {
1168       /* kludge: allow timeout for initial "feature" commands */
1169       FreezeUI();
1170       DisplayMessage("", _("Starting chess program"));
1171       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1172     }
1173 }
1174
1175 void
1176 InitBackEnd3 P((void))
1177 {
1178     GameMode initialMode;
1179     char buf[MSG_SIZ];
1180     int err, len;
1181
1182     InitChessProgram(&first, startedFromSetupPosition);
1183
1184     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1185         free(programVersion);
1186         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1187         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1188     }
1189
1190     if (appData.icsActive) {
1191 #ifdef WIN32
1192         /* [DM] Make a console window if needed [HGM] merged ifs */
1193         ConsoleCreate();
1194 #endif
1195         err = establish();
1196         if (err != 0)
1197           {
1198             if (*appData.icsCommPort != NULLCHAR)
1199               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1200                              appData.icsCommPort);
1201             else
1202               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1203                         appData.icsHost, appData.icsPort);
1204
1205             if( (len > MSG_SIZ) && appData.debugMode )
1206               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1207
1208             DisplayFatalError(buf, err, 1);
1209             return;
1210         }
1211         SetICSMode();
1212         telnetISR =
1213           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1214         fromUserISR =
1215           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1216         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1217             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1218     } else if (appData.noChessProgram) {
1219         SetNCPMode();
1220     } else {
1221         SetGNUMode();
1222     }
1223
1224     if (*appData.cmailGameName != NULLCHAR) {
1225         SetCmailMode();
1226         OpenLoopback(&cmailPR);
1227         cmailISR =
1228           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1229     }
1230
1231     ThawUI();
1232     DisplayMessage("", "");
1233     if (StrCaseCmp(appData.initialMode, "") == 0) {
1234       initialMode = BeginningOfGame;
1235     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1236       initialMode = TwoMachinesPlay;
1237     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1238       initialMode = AnalyzeFile;
1239     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1240       initialMode = AnalyzeMode;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1242       initialMode = MachinePlaysWhite;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1244       initialMode = MachinePlaysBlack;
1245     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1246       initialMode = EditGame;
1247     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1248       initialMode = EditPosition;
1249     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1250       initialMode = Training;
1251     } else {
1252       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1253       if( (len > MSG_SIZ) && appData.debugMode )
1254         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1255
1256       DisplayFatalError(buf, 0, 2);
1257       return;
1258     }
1259
1260     if (appData.matchMode) {
1261         /* Set up machine vs. machine match */
1262         if (appData.noChessProgram) {
1263             DisplayFatalError(_("Can't have a match with no chess programs"),
1264                               0, 2);
1265             return;
1266         }
1267         matchMode = TRUE;
1268         matchGame = 1;
1269         if (*appData.loadGameFile != NULLCHAR) {
1270             int index = appData.loadGameIndex; // [HGM] autoinc
1271             if(index<0) lastIndex = index = 1;
1272             if (!LoadGameFromFile(appData.loadGameFile,
1273                                   index,
1274                                   appData.loadGameFile, FALSE)) {
1275                 DisplayFatalError(_("Bad game file"), 0, 1);
1276                 return;
1277             }
1278         } else if (*appData.loadPositionFile != NULLCHAR) {
1279             int index = appData.loadPositionIndex; // [HGM] autoinc
1280             if(index<0) lastIndex = index = 1;
1281             if (!LoadPositionFromFile(appData.loadPositionFile,
1282                                       index,
1283                                       appData.loadPositionFile)) {
1284                 DisplayFatalError(_("Bad position file"), 0, 1);
1285                 return;
1286             }
1287         }
1288         TwoMachinesEvent();
1289     } else if (*appData.cmailGameName != NULLCHAR) {
1290         /* Set up cmail mode */
1291         ReloadCmailMsgEvent(TRUE);
1292     } else {
1293         /* Set up other modes */
1294         if (initialMode == AnalyzeFile) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1297             return;
1298           }
1299         }
1300         if (*appData.loadGameFile != NULLCHAR) {
1301             (void) LoadGameFromFile(appData.loadGameFile,
1302                                     appData.loadGameIndex,
1303                                     appData.loadGameFile, TRUE);
1304         } else if (*appData.loadPositionFile != NULLCHAR) {
1305             (void) LoadPositionFromFile(appData.loadPositionFile,
1306                                         appData.loadPositionIndex,
1307                                         appData.loadPositionFile);
1308             /* [HGM] try to make self-starting even after FEN load */
1309             /* to allow automatic setup of fairy variants with wtm */
1310             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1311                 gameMode = BeginningOfGame;
1312                 setboardSpoiledMachineBlack = 1;
1313             }
1314             /* [HGM] loadPos: make that every new game uses the setup */
1315             /* from file as long as we do not switch variant          */
1316             if(!blackPlaysFirst) {
1317                 startedFromPositionFile = TRUE;
1318                 CopyBoard(filePosition, boards[0]);
1319             }
1320         }
1321         if (initialMode == AnalyzeMode) {
1322           if (appData.noChessProgram) {
1323             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1324             return;
1325           }
1326           if (appData.icsActive) {
1327             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1328             return;
1329           }
1330           AnalyzeModeEvent();
1331         } else if (initialMode == AnalyzeFile) {
1332           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1333           ShowThinkingEvent();
1334           AnalyzeFileEvent();
1335           AnalysisPeriodicEvent(1);
1336         } else if (initialMode == MachinePlaysWhite) {
1337           if (appData.noChessProgram) {
1338             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1339                               0, 2);
1340             return;
1341           }
1342           if (appData.icsActive) {
1343             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1344                               0, 2);
1345             return;
1346           }
1347           MachineWhiteEvent();
1348         } else if (initialMode == MachinePlaysBlack) {
1349           if (appData.noChessProgram) {
1350             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1351                               0, 2);
1352             return;
1353           }
1354           if (appData.icsActive) {
1355             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1356                               0, 2);
1357             return;
1358           }
1359           MachineBlackEvent();
1360         } else if (initialMode == TwoMachinesPlay) {
1361           if (appData.noChessProgram) {
1362             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1363                               0, 2);
1364             return;
1365           }
1366           if (appData.icsActive) {
1367             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1368                               0, 2);
1369             return;
1370           }
1371           TwoMachinesEvent();
1372         } else if (initialMode == EditGame) {
1373           EditGameEvent();
1374         } else if (initialMode == EditPosition) {
1375           EditPositionEvent();
1376         } else if (initialMode == Training) {
1377           if (*appData.loadGameFile == NULLCHAR) {
1378             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379             return;
1380           }
1381           TrainingEvent();
1382         }
1383     }
1384 }
1385
1386 /*
1387  * Establish will establish a contact to a remote host.port.
1388  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1389  *  used to talk to the host.
1390  * Returns 0 if okay, error code if not.
1391  */
1392 int
1393 establish()
1394 {
1395     char buf[MSG_SIZ];
1396
1397     if (*appData.icsCommPort != NULLCHAR) {
1398         /* Talk to the host through a serial comm port */
1399         return OpenCommPort(appData.icsCommPort, &icsPR);
1400
1401     } else if (*appData.gateway != NULLCHAR) {
1402         if (*appData.remoteShell == NULLCHAR) {
1403             /* Use the rcmd protocol to run telnet program on a gateway host */
1404             snprintf(buf, sizeof(buf), "%s %s %s",
1405                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1406             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1407
1408         } else {
1409             /* Use the rsh program to run telnet program on a gateway host */
1410             if (*appData.remoteUser == NULLCHAR) {
1411                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1412                         appData.gateway, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             } else {
1415                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1416                         appData.remoteShell, appData.gateway,
1417                         appData.remoteUser, appData.telnetProgram,
1418                         appData.icsHost, appData.icsPort);
1419             }
1420             return StartChildProcess(buf, "", &icsPR);
1421
1422         }
1423     } else if (appData.useTelnet) {
1424         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1425
1426     } else {
1427         /* TCP socket interface differs somewhat between
1428            Unix and NT; handle details in the front end.
1429            */
1430         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1431     }
1432 }
1433
1434 void EscapeExpand(char *p, char *q)
1435 {       // [HGM] initstring: routine to shape up string arguments
1436         while(*p++ = *q++) if(p[-1] == '\\')
1437             switch(*q++) {
1438                 case 'n': p[-1] = '\n'; break;
1439                 case 'r': p[-1] = '\r'; break;
1440                 case 't': p[-1] = '\t'; break;
1441                 case '\\': p[-1] = '\\'; break;
1442                 case 0: *p = 0; return;
1443                 default: p[-1] = q[-1]; break;
1444             }
1445 }
1446
1447 void
1448 show_bytes(fp, buf, count)
1449      FILE *fp;
1450      char *buf;
1451      int count;
1452 {
1453     while (count--) {
1454         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1455             fprintf(fp, "\\%03o", *buf & 0xff);
1456         } else {
1457             putc(*buf, fp);
1458         }
1459         buf++;
1460     }
1461     fflush(fp);
1462 }
1463
1464 /* Returns an errno value */
1465 int
1466 OutputMaybeTelnet(pr, message, count, outError)
1467      ProcRef pr;
1468      char *message;
1469      int count;
1470      int *outError;
1471 {
1472     char buf[8192], *p, *q, *buflim;
1473     int left, newcount, outcount;
1474
1475     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1476         *appData.gateway != NULLCHAR) {
1477         if (appData.debugMode) {
1478             fprintf(debugFP, ">ICS: ");
1479             show_bytes(debugFP, message, count);
1480             fprintf(debugFP, "\n");
1481         }
1482         return OutputToProcess(pr, message, count, outError);
1483     }
1484
1485     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1486     p = message;
1487     q = buf;
1488     left = count;
1489     newcount = 0;
1490     while (left) {
1491         if (q >= buflim) {
1492             if (appData.debugMode) {
1493                 fprintf(debugFP, ">ICS: ");
1494                 show_bytes(debugFP, buf, newcount);
1495                 fprintf(debugFP, "\n");
1496             }
1497             outcount = OutputToProcess(pr, buf, newcount, outError);
1498             if (outcount < newcount) return -1; /* to be sure */
1499             q = buf;
1500             newcount = 0;
1501         }
1502         if (*p == '\n') {
1503             *q++ = '\r';
1504             newcount++;
1505         } else if (((unsigned char) *p) == TN_IAC) {
1506             *q++ = (char) TN_IAC;
1507             newcount ++;
1508         }
1509         *q++ = *p++;
1510         newcount++;
1511         left--;
1512     }
1513     if (appData.debugMode) {
1514         fprintf(debugFP, ">ICS: ");
1515         show_bytes(debugFP, buf, newcount);
1516         fprintf(debugFP, "\n");
1517     }
1518     outcount = OutputToProcess(pr, buf, newcount, outError);
1519     if (outcount < newcount) return -1; /* to be sure */
1520     return count;
1521 }
1522
1523 void
1524 read_from_player(isr, closure, message, count, error)
1525      InputSourceRef isr;
1526      VOIDSTAR closure;
1527      char *message;
1528      int count;
1529      int error;
1530 {
1531     int outError, outCount;
1532     static int gotEof = 0;
1533
1534     /* Pass data read from player on to ICS */
1535     if (count > 0) {
1536         gotEof = 0;
1537         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1538         if (outCount < count) {
1539             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540         }
1541     } else if (count < 0) {
1542         RemoveInputSource(isr);
1543         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1544     } else if (gotEof++ > 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1547     }
1548 }
1549
1550 void
1551 KeepAlive()
1552 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1553     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1554     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1555     SendToICS("date\n");
1556     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 }
1558
1559 /* added routine for printf style output to ics */
1560 void ics_printf(char *format, ...)
1561 {
1562     char buffer[MSG_SIZ];
1563     va_list args;
1564
1565     va_start(args, format);
1566     vsnprintf(buffer, sizeof(buffer), format, args);
1567     buffer[sizeof(buffer)-1] = '\0';
1568     SendToICS(buffer);
1569     va_end(args);
1570 }
1571
1572 void
1573 SendToICS(s)
1574      char *s;
1575 {
1576     int count, outCount, outError;
1577
1578     if (icsPR == NULL) return;
1579
1580     count = strlen(s);
1581     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1582     if (outCount < count) {
1583         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1584     }
1585 }
1586
1587 /* This is used for sending logon scripts to the ICS. Sending
1588    without a delay causes problems when using timestamp on ICC
1589    (at least on my machine). */
1590 void
1591 SendToICSDelayed(s,msdelay)
1592      char *s;
1593      long msdelay;
1594 {
1595     int count, outCount, outError;
1596
1597     if (icsPR == NULL) return;
1598
1599     count = strlen(s);
1600     if (appData.debugMode) {
1601         fprintf(debugFP, ">ICS: ");
1602         show_bytes(debugFP, s, count);
1603         fprintf(debugFP, "\n");
1604     }
1605     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1606                                       msdelay);
1607     if (outCount < count) {
1608         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1609     }
1610 }
1611
1612
1613 /* Remove all highlighting escape sequences in s
1614    Also deletes any suffix starting with '('
1615    */
1616 char *
1617 StripHighlightAndTitle(s)
1618      char *s;
1619 {
1620     static char retbuf[MSG_SIZ];
1621     char *p = retbuf;
1622
1623     while (*s != NULLCHAR) {
1624         while (*s == '\033') {
1625             while (*s != NULLCHAR && !isalpha(*s)) s++;
1626             if (*s != NULLCHAR) s++;
1627         }
1628         while (*s != NULLCHAR && *s != '\033') {
1629             if (*s == '(' || *s == '[') {
1630                 *p = NULLCHAR;
1631                 return retbuf;
1632             }
1633             *p++ = *s++;
1634         }
1635     }
1636     *p = NULLCHAR;
1637     return retbuf;
1638 }
1639
1640 /* Remove all highlighting escape sequences in s */
1641 char *
1642 StripHighlight(s)
1643      char *s;
1644 {
1645     static char retbuf[MSG_SIZ];
1646     char *p = retbuf;
1647
1648     while (*s != NULLCHAR) {
1649         while (*s == '\033') {
1650             while (*s != NULLCHAR && !isalpha(*s)) s++;
1651             if (*s != NULLCHAR) s++;
1652         }
1653         while (*s != NULLCHAR && *s != '\033') {
1654             *p++ = *s++;
1655         }
1656     }
1657     *p = NULLCHAR;
1658     return retbuf;
1659 }
1660
1661 char *variantNames[] = VARIANT_NAMES;
1662 char *
1663 VariantName(v)
1664      VariantClass v;
1665 {
1666     return variantNames[v];
1667 }
1668
1669
1670 /* Identify a variant from the strings the chess servers use or the
1671    PGN Variant tag names we use. */
1672 VariantClass
1673 StringToVariant(e)
1674      char *e;
1675 {
1676     char *p;
1677     int wnum = -1;
1678     VariantClass v = VariantNormal;
1679     int i, found = FALSE;
1680     char buf[MSG_SIZ];
1681     int len;
1682
1683     if (!e) return v;
1684
1685     /* [HGM] skip over optional board-size prefixes */
1686     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1687         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1688         while( *e++ != '_');
1689     }
1690
1691     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1692         v = VariantNormal;
1693         found = TRUE;
1694     } else
1695     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1696       if (StrCaseStr(e, variantNames[i])) {
1697         v = (VariantClass) i;
1698         found = TRUE;
1699         break;
1700       }
1701     }
1702
1703     if (!found) {
1704       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1705           || StrCaseStr(e, "wild/fr")
1706           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1707         v = VariantFischeRandom;
1708       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1709                  (i = 1, p = StrCaseStr(e, "w"))) {
1710         p += i;
1711         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1712         if (isdigit(*p)) {
1713           wnum = atoi(p);
1714         } else {
1715           wnum = -1;
1716         }
1717         switch (wnum) {
1718         case 0: /* FICS only, actually */
1719         case 1:
1720           /* Castling legal even if K starts on d-file */
1721           v = VariantWildCastle;
1722           break;
1723         case 2:
1724         case 3:
1725         case 4:
1726           /* Castling illegal even if K & R happen to start in
1727              normal positions. */
1728           v = VariantNoCastle;
1729           break;
1730         case 5:
1731         case 7:
1732         case 8:
1733         case 10:
1734         case 11:
1735         case 12:
1736         case 13:
1737         case 14:
1738         case 15:
1739         case 18:
1740         case 19:
1741           /* Castling legal iff K & R start in normal positions */
1742           v = VariantNormal;
1743           break;
1744         case 6:
1745         case 20:
1746         case 21:
1747           /* Special wilds for position setup; unclear what to do here */
1748           v = VariantLoadable;
1749           break;
1750         case 9:
1751           /* Bizarre ICC game */
1752           v = VariantTwoKings;
1753           break;
1754         case 16:
1755           v = VariantKriegspiel;
1756           break;
1757         case 17:
1758           v = VariantLosers;
1759           break;
1760         case 22:
1761           v = VariantFischeRandom;
1762           break;
1763         case 23:
1764           v = VariantCrazyhouse;
1765           break;
1766         case 24:
1767           v = VariantBughouse;
1768           break;
1769         case 25:
1770           v = Variant3Check;
1771           break;
1772         case 26:
1773           /* Not quite the same as FICS suicide! */
1774           v = VariantGiveaway;
1775           break;
1776         case 27:
1777           v = VariantAtomic;
1778           break;
1779         case 28:
1780           v = VariantShatranj;
1781           break;
1782
1783         /* Temporary names for future ICC types.  The name *will* change in
1784            the next xboard/WinBoard release after ICC defines it. */
1785         case 29:
1786           v = Variant29;
1787           break;
1788         case 30:
1789           v = Variant30;
1790           break;
1791         case 31:
1792           v = Variant31;
1793           break;
1794         case 32:
1795           v = Variant32;
1796           break;
1797         case 33:
1798           v = Variant33;
1799           break;
1800         case 34:
1801           v = Variant34;
1802           break;
1803         case 35:
1804           v = Variant35;
1805           break;
1806         case 36:
1807           v = Variant36;
1808           break;
1809         case 37:
1810           v = VariantShogi;
1811           break;
1812         case 38:
1813           v = VariantXiangqi;
1814           break;
1815         case 39:
1816           v = VariantCourier;
1817           break;
1818         case 40:
1819           v = VariantGothic;
1820           break;
1821         case 41:
1822           v = VariantCapablanca;
1823           break;
1824         case 42:
1825           v = VariantKnightmate;
1826           break;
1827         case 43:
1828           v = VariantFairy;
1829           break;
1830         case 44:
1831           v = VariantCylinder;
1832           break;
1833         case 45:
1834           v = VariantFalcon;
1835           break;
1836         case 46:
1837           v = VariantCapaRandom;
1838           break;
1839         case 47:
1840           v = VariantBerolina;
1841           break;
1842         case 48:
1843           v = VariantJanus;
1844           break;
1845         case 49:
1846           v = VariantSuper;
1847           break;
1848         case 50:
1849           v = VariantGreat;
1850           break;
1851         case -1:
1852           /* Found "wild" or "w" in the string but no number;
1853              must assume it's normal chess. */
1854           v = VariantNormal;
1855           break;
1856         default:
1857           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1858           if( (len > MSG_SIZ) && appData.debugMode )
1859             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1860
1861           DisplayError(buf, 0);
1862           v = VariantUnknown;
1863           break;
1864         }
1865       }
1866     }
1867     if (appData.debugMode) {
1868       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1869               e, wnum, VariantName(v));
1870     }
1871     return v;
1872 }
1873
1874 static int leftover_start = 0, leftover_len = 0;
1875 char star_match[STAR_MATCH_N][MSG_SIZ];
1876
1877 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1878    advance *index beyond it, and set leftover_start to the new value of
1879    *index; else return FALSE.  If pattern contains the character '*', it
1880    matches any sequence of characters not containing '\r', '\n', or the
1881    character following the '*' (if any), and the matched sequence(s) are
1882    copied into star_match.
1883    */
1884 int
1885 looking_at(buf, index, pattern)
1886      char *buf;
1887      int *index;
1888      char *pattern;
1889 {
1890     char *bufp = &buf[*index], *patternp = pattern;
1891     int star_count = 0;
1892     char *matchp = star_match[0];
1893
1894     for (;;) {
1895         if (*patternp == NULLCHAR) {
1896             *index = leftover_start = bufp - buf;
1897             *matchp = NULLCHAR;
1898             return TRUE;
1899         }
1900         if (*bufp == NULLCHAR) return FALSE;
1901         if (*patternp == '*') {
1902             if (*bufp == *(patternp + 1)) {
1903                 *matchp = NULLCHAR;
1904                 matchp = star_match[++star_count];
1905                 patternp += 2;
1906                 bufp++;
1907                 continue;
1908             } else if (*bufp == '\n' || *bufp == '\r') {
1909                 patternp++;
1910                 if (*patternp == NULLCHAR)
1911                   continue;
1912                 else
1913                   return FALSE;
1914             } else {
1915                 *matchp++ = *bufp++;
1916                 continue;
1917             }
1918         }
1919         if (*patternp != *bufp) return FALSE;
1920         patternp++;
1921         bufp++;
1922     }
1923 }
1924
1925 void
1926 SendToPlayer(data, length)
1927      char *data;
1928      int length;
1929 {
1930     int error, outCount;
1931     outCount = OutputToProcess(NoProc, data, length, &error);
1932     if (outCount < length) {
1933         DisplayFatalError(_("Error writing to display"), error, 1);
1934     }
1935 }
1936
1937 void
1938 PackHolding(packed, holding)
1939      char packed[];
1940      char *holding;
1941 {
1942     char *p = holding;
1943     char *q = packed;
1944     int runlength = 0;
1945     int curr = 9999;
1946     do {
1947         if (*p == curr) {
1948             runlength++;
1949         } else {
1950             switch (runlength) {
1951               case 0:
1952                 break;
1953               case 1:
1954                 *q++ = curr;
1955                 break;
1956               case 2:
1957                 *q++ = curr;
1958                 *q++ = curr;
1959                 break;
1960               default:
1961                 sprintf(q, "%d", runlength);
1962                 while (*q) q++;
1963                 *q++ = curr;
1964                 break;
1965             }
1966             runlength = 1;
1967             curr = *p;
1968         }
1969     } while (*p++);
1970     *q = NULLCHAR;
1971 }
1972
1973 /* Telnet protocol requests from the front end */
1974 void
1975 TelnetRequest(ddww, option)
1976      unsigned char ddww, option;
1977 {
1978     unsigned char msg[3];
1979     int outCount, outError;
1980
1981     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1982
1983     if (appData.debugMode) {
1984         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1985         switch (ddww) {
1986           case TN_DO:
1987             ddwwStr = "DO";
1988             break;
1989           case TN_DONT:
1990             ddwwStr = "DONT";
1991             break;
1992           case TN_WILL:
1993             ddwwStr = "WILL";
1994             break;
1995           case TN_WONT:
1996             ddwwStr = "WONT";
1997             break;
1998           default:
1999             ddwwStr = buf1;
2000             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001             break;
2002         }
2003         switch (option) {
2004           case TN_ECHO:
2005             optionStr = "ECHO";
2006             break;
2007           default:
2008             optionStr = buf2;
2009             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2010             break;
2011         }
2012         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2013     }
2014     msg[0] = TN_IAC;
2015     msg[1] = ddww;
2016     msg[2] = option;
2017     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2018     if (outCount < 3) {
2019         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020     }
2021 }
2022
2023 void
2024 DoEcho()
2025 {
2026     if (!appData.icsActive) return;
2027     TelnetRequest(TN_DO, TN_ECHO);
2028 }
2029
2030 void
2031 DontEcho()
2032 {
2033     if (!appData.icsActive) return;
2034     TelnetRequest(TN_DONT, TN_ECHO);
2035 }
2036
2037 void
2038 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2039 {
2040     /* put the holdings sent to us by the server on the board holdings area */
2041     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2042     char p;
2043     ChessSquare piece;
2044
2045     if(gameInfo.holdingsWidth < 2)  return;
2046     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2047         return; // prevent overwriting by pre-board holdings
2048
2049     if( (int)lowestPiece >= BlackPawn ) {
2050         holdingsColumn = 0;
2051         countsColumn = 1;
2052         holdingsStartRow = BOARD_HEIGHT-1;
2053         direction = -1;
2054     } else {
2055         holdingsColumn = BOARD_WIDTH-1;
2056         countsColumn = BOARD_WIDTH-2;
2057         holdingsStartRow = 0;
2058         direction = 1;
2059     }
2060
2061     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2062         board[i][holdingsColumn] = EmptySquare;
2063         board[i][countsColumn]   = (ChessSquare) 0;
2064     }
2065     while( (p=*holdings++) != NULLCHAR ) {
2066         piece = CharToPiece( ToUpper(p) );
2067         if(piece == EmptySquare) continue;
2068         /*j = (int) piece - (int) WhitePawn;*/
2069         j = PieceToNumber(piece);
2070         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2071         if(j < 0) continue;               /* should not happen */
2072         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2073         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2074         board[holdingsStartRow+j*direction][countsColumn]++;
2075     }
2076 }
2077
2078
2079 void
2080 VariantSwitch(Board board, VariantClass newVariant)
2081 {
2082    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2083    static Board oldBoard;
2084
2085    startedFromPositionFile = FALSE;
2086    if(gameInfo.variant == newVariant) return;
2087
2088    /* [HGM] This routine is called each time an assignment is made to
2089     * gameInfo.variant during a game, to make sure the board sizes
2090     * are set to match the new variant. If that means adding or deleting
2091     * holdings, we shift the playing board accordingly
2092     * This kludge is needed because in ICS observe mode, we get boards
2093     * of an ongoing game without knowing the variant, and learn about the
2094     * latter only later. This can be because of the move list we requested,
2095     * in which case the game history is refilled from the beginning anyway,
2096     * but also when receiving holdings of a crazyhouse game. In the latter
2097     * case we want to add those holdings to the already received position.
2098     */
2099
2100
2101    if (appData.debugMode) {
2102      fprintf(debugFP, "Switch board from %s to %s\n",
2103              VariantName(gameInfo.variant), VariantName(newVariant));
2104      setbuf(debugFP, NULL);
2105    }
2106    shuffleOpenings = 0;       /* [HGM] shuffle */
2107    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2108    switch(newVariant)
2109      {
2110      case VariantShogi:
2111        newWidth = 9;  newHeight = 9;
2112        gameInfo.holdingsSize = 7;
2113      case VariantBughouse:
2114      case VariantCrazyhouse:
2115        newHoldingsWidth = 2; break;
2116      case VariantGreat:
2117        newWidth = 10;
2118      case VariantSuper:
2119        newHoldingsWidth = 2;
2120        gameInfo.holdingsSize = 8;
2121        break;
2122      case VariantGothic:
2123      case VariantCapablanca:
2124      case VariantCapaRandom:
2125        newWidth = 10;
2126      default:
2127        newHoldingsWidth = gameInfo.holdingsSize = 0;
2128      };
2129
2130    if(newWidth  != gameInfo.boardWidth  ||
2131       newHeight != gameInfo.boardHeight ||
2132       newHoldingsWidth != gameInfo.holdingsWidth ) {
2133
2134      /* shift position to new playing area, if needed */
2135      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2136        for(i=0; i<BOARD_HEIGHT; i++)
2137          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2138            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2139              board[i][j];
2140        for(i=0; i<newHeight; i++) {
2141          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2142          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2143        }
2144      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2145        for(i=0; i<BOARD_HEIGHT; i++)
2146          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2147            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148              board[i][j];
2149      }
2150      gameInfo.boardWidth  = newWidth;
2151      gameInfo.boardHeight = newHeight;
2152      gameInfo.holdingsWidth = newHoldingsWidth;
2153      gameInfo.variant = newVariant;
2154      InitDrawingSizes(-2, 0);
2155    } else gameInfo.variant = newVariant;
2156    CopyBoard(oldBoard, board);   // remember correctly formatted board
2157      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2158    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2159 }
2160
2161 static int loggedOn = FALSE;
2162
2163 /*-- Game start info cache: --*/
2164 int gs_gamenum;
2165 char gs_kind[MSG_SIZ];
2166 static char player1Name[128] = "";
2167 static char player2Name[128] = "";
2168 static char cont_seq[] = "\n\\   ";
2169 static int player1Rating = -1;
2170 static int player2Rating = -1;
2171 /*----------------------------*/
2172
2173 ColorClass curColor = ColorNormal;
2174 int suppressKibitz = 0;
2175
2176 // [HGM] seekgraph
2177 Boolean soughtPending = FALSE;
2178 Boolean seekGraphUp;
2179 #define MAX_SEEK_ADS 200
2180 #define SQUARE 0x80
2181 char *seekAdList[MAX_SEEK_ADS];
2182 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2183 float tcList[MAX_SEEK_ADS];
2184 char colorList[MAX_SEEK_ADS];
2185 int nrOfSeekAds = 0;
2186 int minRating = 1010, maxRating = 2800;
2187 int hMargin = 10, vMargin = 20, h, w;
2188 extern int squareSize, lineGap;
2189
2190 void
2191 PlotSeekAd(int i)
2192 {
2193         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2194         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2195         if(r < minRating+100 && r >=0 ) r = minRating+100;
2196         if(r > maxRating) r = maxRating;
2197         if(tc < 1.) tc = 1.;
2198         if(tc > 95.) tc = 95.;
2199         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2200         y = ((double)r - minRating)/(maxRating - minRating)
2201             * (h-vMargin-squareSize/8-1) + vMargin;
2202         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2203         if(strstr(seekAdList[i], " u ")) color = 1;
2204         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2205            !strstr(seekAdList[i], "bullet") &&
2206            !strstr(seekAdList[i], "blitz") &&
2207            !strstr(seekAdList[i], "standard") ) color = 2;
2208         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2209         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2210 }
2211
2212 void
2213 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2214 {
2215         char buf[MSG_SIZ], *ext = "";
2216         VariantClass v = StringToVariant(type);
2217         if(strstr(type, "wild")) {
2218             ext = type + 4; // append wild number
2219             if(v == VariantFischeRandom) type = "chess960"; else
2220             if(v == VariantLoadable) type = "setup"; else
2221             type = VariantName(v);
2222         }
2223         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2224         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2225             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2226             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2227             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2228             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2229             seekNrList[nrOfSeekAds] = nr;
2230             zList[nrOfSeekAds] = 0;
2231             seekAdList[nrOfSeekAds++] = StrSave(buf);
2232             if(plot) PlotSeekAd(nrOfSeekAds-1);
2233         }
2234 }
2235
2236 void
2237 EraseSeekDot(int i)
2238 {
2239     int x = xList[i], y = yList[i], d=squareSize/4, k;
2240     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2241     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2242     // now replot every dot that overlapped
2243     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2244         int xx = xList[k], yy = yList[k];
2245         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2246             DrawSeekDot(xx, yy, colorList[k]);
2247     }
2248 }
2249
2250 void
2251 RemoveSeekAd(int nr)
2252 {
2253         int i;
2254         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2255             EraseSeekDot(i);
2256             if(seekAdList[i]) free(seekAdList[i]);
2257             seekAdList[i] = seekAdList[--nrOfSeekAds];
2258             seekNrList[i] = seekNrList[nrOfSeekAds];
2259             ratingList[i] = ratingList[nrOfSeekAds];
2260             colorList[i]  = colorList[nrOfSeekAds];
2261             tcList[i] = tcList[nrOfSeekAds];
2262             xList[i]  = xList[nrOfSeekAds];
2263             yList[i]  = yList[nrOfSeekAds];
2264             zList[i]  = zList[nrOfSeekAds];
2265             seekAdList[nrOfSeekAds] = NULL;
2266             break;
2267         }
2268 }
2269
2270 Boolean
2271 MatchSoughtLine(char *line)
2272 {
2273     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2274     int nr, base, inc, u=0; char dummy;
2275
2276     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2277        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2278        (u=1) &&
2279        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2281         // match: compact and save the line
2282         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2283         return TRUE;
2284     }
2285     return FALSE;
2286 }
2287
2288 int
2289 DrawSeekGraph()
2290 {
2291     int i;
2292     if(!seekGraphUp) return FALSE;
2293     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2294     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2295
2296     DrawSeekBackground(0, 0, w, h);
2297     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2298     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2299     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2300         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2301         yy = h-1-yy;
2302         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2303         if(i%500 == 0) {
2304             char buf[MSG_SIZ];
2305             snprintf(buf, MSG_SIZ, "%d", i);
2306             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2307         }
2308     }
2309     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2310     for(i=1; i<100; i+=(i<10?1:5)) {
2311         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2312         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2313         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2317         }
2318     }
2319     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2320     return TRUE;
2321 }
2322
2323 int SeekGraphClick(ClickType click, int x, int y, int moving)
2324 {
2325     static int lastDown = 0, displayed = 0, lastSecond;
2326     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2327         if(click == Release || moving) return FALSE;
2328         nrOfSeekAds = 0;
2329         soughtPending = TRUE;
2330         SendToICS(ics_prefix);
2331         SendToICS("sought\n"); // should this be "sought all"?
2332     } else { // issue challenge based on clicked ad
2333         int dist = 10000; int i, closest = 0, second = 0;
2334         for(i=0; i<nrOfSeekAds; i++) {
2335             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2336             if(d < dist) { dist = d; closest = i; }
2337             second += (d - zList[i] < 120); // count in-range ads
2338             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2339         }
2340         if(dist < 120) {
2341             char buf[MSG_SIZ];
2342             second = (second > 1);
2343             if(displayed != closest || second != lastSecond) {
2344                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2345                 lastSecond = second; displayed = closest;
2346             }
2347             if(click == Press) {
2348                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2349                 lastDown = closest;
2350                 return TRUE;
2351             } // on press 'hit', only show info
2352             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2353             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2354             SendToICS(ics_prefix);
2355             SendToICS(buf);
2356             return TRUE; // let incoming board of started game pop down the graph
2357         } else if(click == Release) { // release 'miss' is ignored
2358             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2359             if(moving == 2) { // right up-click
2360                 nrOfSeekAds = 0; // refresh graph
2361                 soughtPending = TRUE;
2362                 SendToICS(ics_prefix);
2363                 SendToICS("sought\n"); // should this be "sought all"?
2364             }
2365             return TRUE;
2366         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2367         // press miss or release hit 'pop down' seek graph
2368         seekGraphUp = FALSE;
2369         DrawPosition(TRUE, NULL);
2370     }
2371     return TRUE;
2372 }
2373
2374 void
2375 read_from_ics(isr, closure, data, count, error)
2376      InputSourceRef isr;
2377      VOIDSTAR closure;
2378      char *data;
2379      int count;
2380      int error;
2381 {
2382 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2383 #define STARTED_NONE 0
2384 #define STARTED_MOVES 1
2385 #define STARTED_BOARD 2
2386 #define STARTED_OBSERVE 3
2387 #define STARTED_HOLDINGS 4
2388 #define STARTED_CHATTER 5
2389 #define STARTED_COMMENT 6
2390 #define STARTED_MOVES_NOHIDE 7
2391
2392     static int started = STARTED_NONE;
2393     static char parse[20000];
2394     static int parse_pos = 0;
2395     static char buf[BUF_SIZE + 1];
2396     static int firstTime = TRUE, intfSet = FALSE;
2397     static ColorClass prevColor = ColorNormal;
2398     static int savingComment = FALSE;
2399     static int cmatch = 0; // continuation sequence match
2400     char *bp;
2401     char str[MSG_SIZ];
2402     int i, oldi;
2403     int buf_len;
2404     int next_out;
2405     int tkind;
2406     int backup;    /* [DM] For zippy color lines */
2407     char *p;
2408     char talker[MSG_SIZ]; // [HGM] chat
2409     int channel;
2410
2411     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2412
2413     if (appData.debugMode) {
2414       if (!error) {
2415         fprintf(debugFP, "<ICS: ");
2416         show_bytes(debugFP, data, count);
2417         fprintf(debugFP, "\n");
2418       }
2419     }
2420
2421     if (appData.debugMode) { int f = forwardMostMove;
2422         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2423                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2424                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2425     }
2426     if (count > 0) {
2427         /* If last read ended with a partial line that we couldn't parse,
2428            prepend it to the new read and try again. */
2429         if (leftover_len > 0) {
2430             for (i=0; i<leftover_len; i++)
2431               buf[i] = buf[leftover_start + i];
2432         }
2433
2434     /* copy new characters into the buffer */
2435     bp = buf + leftover_len;
2436     buf_len=leftover_len;
2437     for (i=0; i<count; i++)
2438     {
2439         // ignore these
2440         if (data[i] == '\r')
2441             continue;
2442
2443         // join lines split by ICS?
2444         if (!appData.noJoin)
2445         {
2446             /*
2447                 Joining just consists of finding matches against the
2448                 continuation sequence, and discarding that sequence
2449                 if found instead of copying it.  So, until a match
2450                 fails, there's nothing to do since it might be the
2451                 complete sequence, and thus, something we don't want
2452                 copied.
2453             */
2454             if (data[i] == cont_seq[cmatch])
2455             {
2456                 cmatch++;
2457                 if (cmatch == strlen(cont_seq))
2458                 {
2459                     cmatch = 0; // complete match.  just reset the counter
2460
2461                     /*
2462                         it's possible for the ICS to not include the space
2463                         at the end of the last word, making our [correct]
2464                         join operation fuse two separate words.  the server
2465                         does this when the space occurs at the width setting.
2466                     */
2467                     if (!buf_len || buf[buf_len-1] != ' ')
2468                     {
2469                         *bp++ = ' ';
2470                         buf_len++;
2471                     }
2472                 }
2473                 continue;
2474             }
2475             else if (cmatch)
2476             {
2477                 /*
2478                     match failed, so we have to copy what matched before
2479                     falling through and copying this character.  In reality,
2480                     this will only ever be just the newline character, but
2481                     it doesn't hurt to be precise.
2482                 */
2483                 strncpy(bp, cont_seq, cmatch);
2484                 bp += cmatch;
2485                 buf_len += cmatch;
2486                 cmatch = 0;
2487             }
2488         }
2489
2490         // copy this char
2491         *bp++ = data[i];
2492         buf_len++;
2493     }
2494
2495         buf[buf_len] = NULLCHAR;
2496 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2497         next_out = 0;
2498         leftover_start = 0;
2499
2500         i = 0;
2501         while (i < buf_len) {
2502             /* Deal with part of the TELNET option negotiation
2503                protocol.  We refuse to do anything beyond the
2504                defaults, except that we allow the WILL ECHO option,
2505                which ICS uses to turn off password echoing when we are
2506                directly connected to it.  We reject this option
2507                if localLineEditing mode is on (always on in xboard)
2508                and we are talking to port 23, which might be a real
2509                telnet server that will try to keep WILL ECHO on permanently.
2510              */
2511             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2512                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2513                 unsigned char option;
2514                 oldi = i;
2515                 switch ((unsigned char) buf[++i]) {
2516                   case TN_WILL:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<WILL ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       case TN_ECHO:
2521                         if (appData.debugMode)
2522                           fprintf(debugFP, "ECHO ");
2523                         /* Reply only if this is a change, according
2524                            to the protocol rules. */
2525                         if (remoteEchoOption) break;
2526                         if (appData.localLineEditing &&
2527                             atoi(appData.icsPort) == TN_PORT) {
2528                             TelnetRequest(TN_DONT, TN_ECHO);
2529                         } else {
2530                             EchoOff();
2531                             TelnetRequest(TN_DO, TN_ECHO);
2532                             remoteEchoOption = TRUE;
2533                         }
2534                         break;
2535                       default:
2536                         if (appData.debugMode)
2537                           fprintf(debugFP, "%d ", option);
2538                         /* Whatever this is, we don't want it. */
2539                         TelnetRequest(TN_DONT, option);
2540                         break;
2541                     }
2542                     break;
2543                   case TN_WONT:
2544                     if (appData.debugMode)
2545                       fprintf(debugFP, "\n<WONT ");
2546                     switch (option = (unsigned char) buf[++i]) {
2547                       case TN_ECHO:
2548                         if (appData.debugMode)
2549                           fprintf(debugFP, "ECHO ");
2550                         /* Reply only if this is a change, according
2551                            to the protocol rules. */
2552                         if (!remoteEchoOption) break;
2553                         EchoOn();
2554                         TelnetRequest(TN_DONT, TN_ECHO);
2555                         remoteEchoOption = FALSE;
2556                         break;
2557                       default:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "%d ", (unsigned char) option);
2560                         /* Whatever this is, it must already be turned
2561                            off, because we never agree to turn on
2562                            anything non-default, so according to the
2563                            protocol rules, we don't reply. */
2564                         break;
2565                     }
2566                     break;
2567                   case TN_DO:
2568                     if (appData.debugMode)
2569                       fprintf(debugFP, "\n<DO ");
2570                     switch (option = (unsigned char) buf[++i]) {
2571                       default:
2572                         /* Whatever this is, we refuse to do it. */
2573                         if (appData.debugMode)
2574                           fprintf(debugFP, "%d ", option);
2575                         TelnetRequest(TN_WONT, option);
2576                         break;
2577                     }
2578                     break;
2579                   case TN_DONT:
2580                     if (appData.debugMode)
2581                       fprintf(debugFP, "\n<DONT ");
2582                     switch (option = (unsigned char) buf[++i]) {
2583                       default:
2584                         if (appData.debugMode)
2585                           fprintf(debugFP, "%d ", option);
2586                         /* Whatever this is, we are already not doing
2587                            it, because we never agree to do anything
2588                            non-default, so according to the protocol
2589                            rules, we don't reply. */
2590                         break;
2591                     }
2592                     break;
2593                   case TN_IAC:
2594                     if (appData.debugMode)
2595                       fprintf(debugFP, "\n<IAC ");
2596                     /* Doubled IAC; pass it through */
2597                     i--;
2598                     break;
2599                   default:
2600                     if (appData.debugMode)
2601                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2602                     /* Drop all other telnet commands on the floor */
2603                     break;
2604                 }
2605                 if (oldi > next_out)
2606                   SendToPlayer(&buf[next_out], oldi - next_out);
2607                 if (++i > next_out)
2608                   next_out = i;
2609                 continue;
2610             }
2611
2612             /* OK, this at least will *usually* work */
2613             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2614                 loggedOn = TRUE;
2615             }
2616
2617             if (loggedOn && !intfSet) {
2618                 if (ics_type == ICS_ICC) {
2619                   snprintf(str, MSG_SIZ,
2620                           "/set-quietly interface %s\n/set-quietly style 12\n",
2621                           programVersion);
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2624                 } else if (ics_type == ICS_CHESSNET) {
2625                   snprintf(str, MSG_SIZ, "/style 12\n");
2626                 } else {
2627                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2628                   strcat(str, programVersion);
2629                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2630                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2631                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2632 #ifdef WIN32
2633                   strcat(str, "$iset nohighlight 1\n");
2634 #endif
2635                   strcat(str, "$iset lock 1\n$style 12\n");
2636                 }
2637                 SendToICS(str);
2638                 NotifyFrontendLogin();
2639                 intfSet = TRUE;
2640             }
2641
2642             if (started == STARTED_COMMENT) {
2643                 /* Accumulate characters in comment */
2644                 parse[parse_pos++] = buf[i];
2645                 if (buf[i] == '\n') {
2646                     parse[parse_pos] = NULLCHAR;
2647                     if(chattingPartner>=0) {
2648                         char mess[MSG_SIZ];
2649                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2650                         OutputChatMessage(chattingPartner, mess);
2651                         chattingPartner = -1;
2652                         next_out = i+1; // [HGM] suppress printing in ICS window
2653                     } else
2654                     if(!suppressKibitz) // [HGM] kibitz
2655                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2656                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2657                         int nrDigit = 0, nrAlph = 0, j;
2658                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2659                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2660                         parse[parse_pos] = NULLCHAR;
2661                         // try to be smart: if it does not look like search info, it should go to
2662                         // ICS interaction window after all, not to engine-output window.
2663                         for(j=0; j<parse_pos; j++) { // count letters and digits
2664                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2665                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2666                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2667                         }
2668                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2669                             int depth=0; float score;
2670                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2671                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2672                                 pvInfoList[forwardMostMove-1].depth = depth;
2673                                 pvInfoList[forwardMostMove-1].score = 100*score;
2674                             }
2675                             OutputKibitz(suppressKibitz, parse);
2676                         } else {
2677                             char tmp[MSG_SIZ];
2678                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2679                             SendToPlayer(tmp, strlen(tmp));
2680                         }
2681                         next_out = i+1; // [HGM] suppress printing in ICS window
2682                     }
2683                     started = STARTED_NONE;
2684                 } else {
2685                     /* Don't match patterns against characters in comment */
2686                     i++;
2687                     continue;
2688                 }
2689             }
2690             if (started == STARTED_CHATTER) {
2691                 if (buf[i] != '\n') {
2692                     /* Don't match patterns against characters in chatter */
2693                     i++;
2694                     continue;
2695                 }
2696                 started = STARTED_NONE;
2697                 if(suppressKibitz) next_out = i+1;
2698             }
2699
2700             /* Kludge to deal with rcmd protocol */
2701             if (firstTime && looking_at(buf, &i, "\001*")) {
2702                 DisplayFatalError(&buf[1], 0, 1);
2703                 continue;
2704             } else {
2705                 firstTime = FALSE;
2706             }
2707
2708             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2709                 ics_type = ICS_ICC;
2710                 ics_prefix = "/";
2711                 if (appData.debugMode)
2712                   fprintf(debugFP, "ics_type %d\n", ics_type);
2713                 continue;
2714             }
2715             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2716                 ics_type = ICS_FICS;
2717                 ics_prefix = "$";
2718                 if (appData.debugMode)
2719                   fprintf(debugFP, "ics_type %d\n", ics_type);
2720                 continue;
2721             }
2722             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2723                 ics_type = ICS_CHESSNET;
2724                 ics_prefix = "/";
2725                 if (appData.debugMode)
2726                   fprintf(debugFP, "ics_type %d\n", ics_type);
2727                 continue;
2728             }
2729
2730             if (!loggedOn &&
2731                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2732                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2733                  looking_at(buf, &i, "will be \"*\""))) {
2734               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2735               continue;
2736             }
2737
2738             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2739               char buf[MSG_SIZ];
2740               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2741               DisplayIcsInteractionTitle(buf);
2742               have_set_title = TRUE;
2743             }
2744
2745             /* skip finger notes */
2746             if (started == STARTED_NONE &&
2747                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2748                  (buf[i] == '1' && buf[i+1] == '0')) &&
2749                 buf[i+2] == ':' && buf[i+3] == ' ') {
2750               started = STARTED_CHATTER;
2751               i += 3;
2752               continue;
2753             }
2754
2755             oldi = i;
2756             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2757             if(appData.seekGraph) {
2758                 if(soughtPending && MatchSoughtLine(buf+i)) {
2759                     i = strstr(buf+i, "rated") - buf;
2760                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761                     next_out = leftover_start = i;
2762                     started = STARTED_CHATTER;
2763                     suppressKibitz = TRUE;
2764                     continue;
2765                 }
2766                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2767                         && looking_at(buf, &i, "* ads displayed")) {
2768                     soughtPending = FALSE;
2769                     seekGraphUp = TRUE;
2770                     DrawSeekGraph();
2771                     continue;
2772                 }
2773                 if(appData.autoRefresh) {
2774                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2775                         int s = (ics_type == ICS_ICC); // ICC format differs
2776                         if(seekGraphUp)
2777                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2778                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2779                         looking_at(buf, &i, "*% "); // eat prompt
2780                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2781                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2782                         next_out = i; // suppress
2783                         continue;
2784                     }
2785                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2786                         char *p = star_match[0];
2787                         while(*p) {
2788                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2789                             while(*p && *p++ != ' '); // next
2790                         }
2791                         looking_at(buf, &i, "*% "); // eat prompt
2792                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2793                         next_out = i;
2794                         continue;
2795                     }
2796                 }
2797             }
2798
2799             /* skip formula vars */
2800             if (started == STARTED_NONE &&
2801                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2802               started = STARTED_CHATTER;
2803               i += 3;
2804               continue;
2805             }
2806
2807             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2808             if (appData.autoKibitz && started == STARTED_NONE &&
2809                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2810                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2811                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2812                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2813                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2814                         suppressKibitz = TRUE;
2815                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = i;
2817                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2818                                 && (gameMode == IcsPlayingWhite)) ||
2819                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2820                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2821                             started = STARTED_CHATTER; // own kibitz we simply discard
2822                         else {
2823                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2824                             parse_pos = 0; parse[0] = NULLCHAR;
2825                             savingComment = TRUE;
2826                             suppressKibitz = gameMode != IcsObserving ? 2 :
2827                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2828                         }
2829                         continue;
2830                 } else
2831                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2832                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2833                          && atoi(star_match[0])) {
2834                     // suppress the acknowledgements of our own autoKibitz
2835                     char *p;
2836                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2837                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2838                     SendToPlayer(star_match[0], strlen(star_match[0]));
2839                     if(looking_at(buf, &i, "*% ")) // eat prompt
2840                         suppressKibitz = FALSE;
2841                     next_out = i;
2842                     continue;
2843                 }
2844             } // [HGM] kibitz: end of patch
2845
2846             // [HGM] chat: intercept tells by users for which we have an open chat window
2847             channel = -1;
2848             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2849                                            looking_at(buf, &i, "* whispers:") ||
2850                                            looking_at(buf, &i, "* kibitzes:") ||
2851                                            looking_at(buf, &i, "* shouts:") ||
2852                                            looking_at(buf, &i, "* c-shouts:") ||
2853                                            looking_at(buf, &i, "--> * ") ||
2854                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2856                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2858                 int p;
2859                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2860                 chattingPartner = -1;
2861
2862                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(channel == atoi(chatPartner[p])) {
2865                     talker[0] = '['; strcat(talker, "] ");
2866                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2867                     chattingPartner = p; break;
2868                     }
2869                 } else
2870                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2871                 for(p=0; p<MAX_CHAT; p++) {
2872                     if(!strcmp("kibitzes", chatPartner[p])) {
2873                         talker[0] = '['; strcat(talker, "] ");
2874                         chattingPartner = p; break;
2875                     }
2876                 } else
2877                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2878                 for(p=0; p<MAX_CHAT; p++) {
2879                     if(!strcmp("whispers", chatPartner[p])) {
2880                         talker[0] = '['; strcat(talker, "] ");
2881                         chattingPartner = p; break;
2882                     }
2883                 } else
2884                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2885                   if(buf[i-8] == '-' && buf[i-3] == 't')
2886                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2887                     if(!strcmp("c-shouts", chatPartner[p])) {
2888                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                   if(chattingPartner < 0)
2893                   for(p=0; p<MAX_CHAT; p++) {
2894                     if(!strcmp("shouts", chatPartner[p])) {
2895                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2896                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2897                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                 }
2902                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2903                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2904                     talker[0] = 0; Colorize(ColorTell, FALSE);
2905                     chattingPartner = p; break;
2906                 }
2907                 if(chattingPartner<0) i = oldi; else {
2908                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2909                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2910                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2911                     started = STARTED_COMMENT;
2912                     parse_pos = 0; parse[0] = NULLCHAR;
2913                     savingComment = 3 + chattingPartner; // counts as TRUE
2914                     suppressKibitz = TRUE;
2915                     continue;
2916                 }
2917             } // [HGM] chat: end of patch
2918
2919             if (appData.zippyTalk || appData.zippyPlay) {
2920                 /* [DM] Backup address for color zippy lines */
2921                 backup = i;
2922 #if ZIPPY
2923                if (loggedOn == TRUE)
2924                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2925                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2926 #endif
2927             } // [DM] 'else { ' deleted
2928                 if (
2929                     /* Regular tells and says */
2930                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2931                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2932                     looking_at(buf, &i, "* says: ") ||
2933                     /* Don't color "message" or "messages" output */
2934                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2935                     looking_at(buf, &i, "*. * at *:*: ") ||
2936                     looking_at(buf, &i, "--* (*:*): ") ||
2937                     /* Message notifications (same color as tells) */
2938                     looking_at(buf, &i, "* has left a message ") ||
2939                     looking_at(buf, &i, "* just sent you a message:\n") ||
2940                     /* Whispers and kibitzes */
2941                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2942                     looking_at(buf, &i, "* kibitzes: ") ||
2943                     /* Channel tells */
2944                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2945
2946                   if (tkind == 1 && strchr(star_match[0], ':')) {
2947                       /* Avoid "tells you:" spoofs in channels */
2948                      tkind = 3;
2949                   }
2950                   if (star_match[0][0] == NULLCHAR ||
2951                       strchr(star_match[0], ' ') ||
2952                       (tkind == 3 && strchr(star_match[1], ' '))) {
2953                     /* Reject bogus matches */
2954                     i = oldi;
2955                   } else {
2956                     if (appData.colorize) {
2957                       if (oldi > next_out) {
2958                         SendToPlayer(&buf[next_out], oldi - next_out);
2959                         next_out = oldi;
2960                       }
2961                       switch (tkind) {
2962                       case 1:
2963                         Colorize(ColorTell, FALSE);
2964                         curColor = ColorTell;
2965                         break;
2966                       case 2:
2967                         Colorize(ColorKibitz, FALSE);
2968                         curColor = ColorKibitz;
2969                         break;
2970                       case 3:
2971                         p = strrchr(star_match[1], '(');
2972                         if (p == NULL) {
2973                           p = star_match[1];
2974                         } else {
2975                           p++;
2976                         }
2977                         if (atoi(p) == 1) {
2978                           Colorize(ColorChannel1, FALSE);
2979                           curColor = ColorChannel1;
2980                         } else {
2981                           Colorize(ColorChannel, FALSE);
2982                           curColor = ColorChannel;
2983                         }
2984                         break;
2985                       case 5:
2986                         curColor = ColorNormal;
2987                         break;
2988                       }
2989                     }
2990                     if (started == STARTED_NONE && appData.autoComment &&
2991                         (gameMode == IcsObserving ||
2992                          gameMode == IcsPlayingWhite ||
2993                          gameMode == IcsPlayingBlack)) {
2994                       parse_pos = i - oldi;
2995                       memcpy(parse, &buf[oldi], parse_pos);
2996                       parse[parse_pos] = NULLCHAR;
2997                       started = STARTED_COMMENT;
2998                       savingComment = TRUE;
2999                     } else {
3000                       started = STARTED_CHATTER;
3001                       savingComment = FALSE;
3002                     }
3003                     loggedOn = TRUE;
3004                     continue;
3005                   }
3006                 }
3007
3008                 if (looking_at(buf, &i, "* s-shouts: ") ||
3009                     looking_at(buf, &i, "* c-shouts: ")) {
3010                     if (appData.colorize) {
3011                         if (oldi > next_out) {
3012                             SendToPlayer(&buf[next_out], oldi - next_out);
3013                             next_out = oldi;
3014                         }
3015                         Colorize(ColorSShout, FALSE);
3016                         curColor = ColorSShout;
3017                     }
3018                     loggedOn = TRUE;
3019                     started = STARTED_CHATTER;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "--->")) {
3024                     loggedOn = TRUE;
3025                     continue;
3026                 }
3027
3028                 if (looking_at(buf, &i, "* shouts: ") ||
3029                     looking_at(buf, &i, "--> ")) {
3030                     if (appData.colorize) {
3031                         if (oldi > next_out) {
3032                             SendToPlayer(&buf[next_out], oldi - next_out);
3033                             next_out = oldi;
3034                         }
3035                         Colorize(ColorShout, FALSE);
3036                         curColor = ColorShout;
3037                     }
3038                     loggedOn = TRUE;
3039                     started = STARTED_CHATTER;
3040                     continue;
3041                 }
3042
3043                 if (looking_at( buf, &i, "Challenge:")) {
3044                     if (appData.colorize) {
3045                         if (oldi > next_out) {
3046                             SendToPlayer(&buf[next_out], oldi - next_out);
3047                             next_out = oldi;
3048                         }
3049                         Colorize(ColorChallenge, FALSE);
3050                         curColor = ColorChallenge;
3051                     }
3052                     loggedOn = TRUE;
3053                     continue;
3054                 }
3055
3056                 if (looking_at(buf, &i, "* offers you") ||
3057                     looking_at(buf, &i, "* offers to be") ||
3058                     looking_at(buf, &i, "* would like to") ||
3059                     looking_at(buf, &i, "* requests to") ||
3060                     looking_at(buf, &i, "Your opponent offers") ||
3061                     looking_at(buf, &i, "Your opponent requests")) {
3062
3063                     if (appData.colorize) {
3064                         if (oldi > next_out) {
3065                             SendToPlayer(&buf[next_out], oldi - next_out);
3066                             next_out = oldi;
3067                         }
3068                         Colorize(ColorRequest, FALSE);
3069                         curColor = ColorRequest;
3070                     }
3071                     continue;
3072                 }
3073
3074                 if (looking_at(buf, &i, "* (*) seeking")) {
3075                     if (appData.colorize) {
3076                         if (oldi > next_out) {
3077                             SendToPlayer(&buf[next_out], oldi - next_out);
3078                             next_out = oldi;
3079                         }
3080                         Colorize(ColorSeek, FALSE);
3081                         curColor = ColorSeek;
3082                     }
3083                     continue;
3084             }
3085
3086             if (looking_at(buf, &i, "\\   ")) {
3087                 if (prevColor != ColorNormal) {
3088                     if (oldi > next_out) {
3089                         SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = oldi;
3091                     }
3092                     Colorize(prevColor, TRUE);
3093                     curColor = prevColor;
3094                 }
3095                 if (savingComment) {
3096                     parse_pos = i - oldi;
3097                     memcpy(parse, &buf[oldi], parse_pos);
3098                     parse[parse_pos] = NULLCHAR;
3099                     started = STARTED_COMMENT;
3100                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3101                         chattingPartner = savingComment - 3; // kludge to remember the box
3102                 } else {
3103                     started = STARTED_CHATTER;
3104                 }
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Black Strength :") ||
3109                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3110                 looking_at(buf, &i, "<10>") ||
3111                 looking_at(buf, &i, "#@#")) {
3112                 /* Wrong board style */
3113                 loggedOn = TRUE;
3114                 SendToICS(ics_prefix);
3115                 SendToICS("set style 12\n");
3116                 SendToICS(ics_prefix);
3117                 SendToICS("refresh\n");
3118                 continue;
3119             }
3120
3121             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3122                 ICSInitScript();
3123                 have_sent_ICS_logon = 1;
3124                 continue;
3125             }
3126
3127             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3128                 (looking_at(buf, &i, "\n<12> ") ||
3129                  looking_at(buf, &i, "<12> "))) {
3130                 loggedOn = TRUE;
3131                 if (oldi > next_out) {
3132                     SendToPlayer(&buf[next_out], oldi - next_out);
3133                 }
3134                 next_out = i;
3135                 started = STARTED_BOARD;
3136                 parse_pos = 0;
3137                 continue;
3138             }
3139
3140             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3141                 looking_at(buf, &i, "<b1> ")) {
3142                 if (oldi > next_out) {
3143                     SendToPlayer(&buf[next_out], oldi - next_out);
3144                 }
3145                 next_out = i;
3146                 started = STARTED_HOLDINGS;
3147                 parse_pos = 0;
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3152                 loggedOn = TRUE;
3153                 /* Header for a move list -- first line */
3154
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     switch (gameMode) {
3158                       case IcsIdle:
3159                       case BeginningOfGame:
3160                         /* User typed "moves" or "oldmoves" while we
3161                            were idle.  Pretend we asked for these
3162                            moves and soak them up so user can step
3163                            through them and/or save them.
3164                            */
3165                         Reset(FALSE, TRUE);
3166                         gameMode = IcsObserving;
3167                         ModeHighlight();
3168                         ics_gamenum = -1;
3169                         ics_getting_history = H_GOT_UNREQ_HEADER;
3170                         break;
3171                       case EditGame: /*?*/
3172                       case EditPosition: /*?*/
3173                         /* Should above feature work in these modes too? */
3174                         /* For now it doesn't */
3175                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3176                         break;
3177                       default:
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                     }
3181                     break;
3182                   case H_REQUESTED:
3183                     /* Is this the right one? */
3184                     if (gameInfo.white && gameInfo.black &&
3185                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3186                         strcmp(gameInfo.black, star_match[2]) == 0) {
3187                         /* All is well */
3188                         ics_getting_history = H_GOT_REQ_HEADER;
3189                     }
3190                     break;
3191                   case H_GOT_REQ_HEADER:
3192                   case H_GOT_UNREQ_HEADER:
3193                   case H_GOT_UNWANTED_HEADER:
3194                   case H_GETTING_MOVES:
3195                     /* Should not happen */
3196                     DisplayError(_("Error gathering move list: two headers"), 0);
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200
3201                 /* Save player ratings into gameInfo if needed */
3202                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3203                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3204                     (gameInfo.whiteRating == -1 ||
3205                      gameInfo.blackRating == -1)) {
3206
3207                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3208                     gameInfo.blackRating = string_to_rating(star_match[3]);
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3211                               gameInfo.whiteRating, gameInfo.blackRating);
3212                 }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i,
3217               "* * match, initial time: * minute*, increment: * second")) {
3218                 /* Header for a move list -- second line */
3219                 /* Initial board will follow if this is a wild game */
3220                 if (gameInfo.event != NULL) free(gameInfo.event);
3221                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3222                 gameInfo.event = StrSave(str);
3223                 /* [HGM] we switched variant. Translate boards if needed. */
3224                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i, "Move  ")) {
3229                 /* Beginning of a move list */
3230                 switch (ics_getting_history) {
3231                   case H_FALSE:
3232                     /* Normally should not happen */
3233                     /* Maybe user hit reset while we were parsing */
3234                     break;
3235                   case H_REQUESTED:
3236                     /* Happens if we are ignoring a move list that is not
3237                      * the one we just requested.  Common if the user
3238                      * tries to observe two games without turning off
3239                      * getMoveList */
3240                     break;
3241                   case H_GETTING_MOVES:
3242                     /* Should not happen */
3243                     DisplayError(_("Error gathering move list: nested"), 0);
3244                     ics_getting_history = H_FALSE;
3245                     break;
3246                   case H_GOT_REQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES;
3249                     parse_pos = 0;
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                     }
3253                     break;
3254                   case H_GOT_UNREQ_HEADER:
3255                     ics_getting_history = H_GETTING_MOVES;
3256                     started = STARTED_MOVES_NOHIDE;
3257                     parse_pos = 0;
3258                     break;
3259                   case H_GOT_UNWANTED_HEADER:
3260                     ics_getting_history = H_FALSE;
3261                     break;
3262                 }
3263                 continue;
3264             }
3265
3266             if (looking_at(buf, &i, "% ") ||
3267                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3268                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3269                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3270                     soughtPending = FALSE;
3271                     seekGraphUp = TRUE;
3272                     DrawSeekGraph();
3273                 }
3274                 if(suppressKibitz) next_out = i;
3275                 savingComment = FALSE;
3276                 suppressKibitz = 0;
3277                 switch (started) {
3278                   case STARTED_MOVES:
3279                   case STARTED_MOVES_NOHIDE:
3280                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3281                     parse[parse_pos + i - oldi] = NULLCHAR;
3282                     ParseGameHistory(parse);
3283 #if ZIPPY
3284                     if (appData.zippyPlay && first.initDone) {
3285                         FeedMovesToProgram(&first, forwardMostMove);
3286                         if (gameMode == IcsPlayingWhite) {
3287                             if (WhiteOnMove(forwardMostMove)) {
3288                                 if (first.sendTime) {
3289                                   if (first.useColors) {
3290                                     SendToProgram("black\n", &first);
3291                                   }
3292                                   SendTimeRemaining(&first, TRUE);
3293                                 }
3294                                 if (first.useColors) {
3295                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3296                                 }
3297                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3298                                 first.maybeThinking = TRUE;
3299                             } else {
3300                                 if (first.usePlayother) {
3301                                   if (first.sendTime) {
3302                                     SendTimeRemaining(&first, TRUE);
3303                                   }
3304                                   SendToProgram("playother\n", &first);
3305                                   firstMove = FALSE;
3306                                 } else {
3307                                   firstMove = TRUE;
3308                                 }
3309                             }
3310                         } else if (gameMode == IcsPlayingBlack) {
3311                             if (!WhiteOnMove(forwardMostMove)) {
3312                                 if (first.sendTime) {
3313                                   if (first.useColors) {
3314                                     SendToProgram("white\n", &first);
3315                                   }
3316                                   SendTimeRemaining(&first, FALSE);
3317                                 }
3318                                 if (first.useColors) {
3319                                   SendToProgram("black\n", &first);
3320                                 }
3321                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3322                                 first.maybeThinking = TRUE;
3323                             } else {
3324                                 if (first.usePlayother) {
3325                                   if (first.sendTime) {
3326                                     SendTimeRemaining(&first, FALSE);
3327                                   }
3328                                   SendToProgram("playother\n", &first);
3329                                   firstMove = FALSE;
3330                                 } else {
3331                                   firstMove = TRUE;
3332                                 }
3333                             }
3334                         }
3335                     }
3336 #endif
3337                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3338                         /* Moves came from oldmoves or moves command
3339                            while we weren't doing anything else.
3340                            */
3341                         currentMove = forwardMostMove;
3342                         ClearHighlights();/*!!could figure this out*/
3343                         flipView = appData.flipView;
3344                         DrawPosition(TRUE, boards[currentMove]);
3345                         DisplayBothClocks();
3346                         snprintf(str, MSG_SIZ, "%s vs. %s",
3347                                 gameInfo.white, gameInfo.black);
3348                         DisplayTitle(str);
3349                         gameMode = IcsIdle;
3350                     } else {
3351                         /* Moves were history of an active game */
3352                         if (gameInfo.resultDetails != NULL) {
3353                             free(gameInfo.resultDetails);
3354                             gameInfo.resultDetails = NULL;
3355                         }
3356                     }
3357                     HistorySet(parseList, backwardMostMove,
3358                                forwardMostMove, currentMove-1);
3359                     DisplayMove(currentMove - 1);
3360                     if (started == STARTED_MOVES) next_out = i;
3361                     started = STARTED_NONE;
3362                     ics_getting_history = H_FALSE;
3363                     break;
3364
3365                   case STARTED_OBSERVE:
3366                     started = STARTED_NONE;
3367                     SendToICS(ics_prefix);
3368                     SendToICS("refresh\n");
3369                     break;
3370
3371                   default:
3372                     break;
3373                 }
3374                 if(bookHit) { // [HGM] book: simulate book reply
3375                     static char bookMove[MSG_SIZ]; // a bit generous?
3376
3377                     programStats.nodes = programStats.depth = programStats.time =
3378                     programStats.score = programStats.got_only_move = 0;
3379                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3380
3381                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3382                     strcat(bookMove, bookHit);
3383                     HandleMachineMove(bookMove, &first);
3384                 }
3385                 continue;
3386             }
3387
3388             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3389                  started == STARTED_HOLDINGS ||
3390                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3391                 /* Accumulate characters in move list or board */
3392                 parse[parse_pos++] = buf[i];
3393             }
3394
3395             /* Start of game messages.  Mostly we detect start of game
3396                when the first board image arrives.  On some versions
3397                of the ICS, though, we need to do a "refresh" after starting
3398                to observe in order to get the current board right away. */
3399             if (looking_at(buf, &i, "Adding game * to observation list")) {
3400                 started = STARTED_OBSERVE;
3401                 continue;
3402             }
3403
3404             /* Handle auto-observe */
3405             if (appData.autoObserve &&
3406                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3407                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3408                 char *player;
3409                 /* Choose the player that was highlighted, if any. */
3410                 if (star_match[0][0] == '\033' ||
3411                     star_match[1][0] != '\033') {
3412                     player = star_match[0];
3413                 } else {
3414                     player = star_match[2];
3415                 }
3416                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3417                         ics_prefix, StripHighlightAndTitle(player));
3418                 SendToICS(str);
3419
3420                 /* Save ratings from notify string */
3421                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3422                 player1Rating = string_to_rating(star_match[1]);
3423                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3424                 player2Rating = string_to_rating(star_match[3]);
3425
3426                 if (appData.debugMode)
3427                   fprintf(debugFP,
3428                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3429                           player1Name, player1Rating,
3430                           player2Name, player2Rating);
3431
3432                 continue;
3433             }
3434
3435             /* Deal with automatic examine mode after a game,
3436                and with IcsObserving -> IcsExamining transition */
3437             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3438                 looking_at(buf, &i, "has made you an examiner of game *")) {
3439
3440                 int gamenum = atoi(star_match[0]);
3441                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3442                     gamenum == ics_gamenum) {
3443                     /* We were already playing or observing this game;
3444                        no need to refetch history */
3445                     gameMode = IcsExamining;
3446                     if (pausing) {
3447                         pauseExamForwardMostMove = forwardMostMove;
3448                     } else if (currentMove < forwardMostMove) {
3449                         ForwardInner(forwardMostMove);
3450                     }
3451                 } else {
3452                     /* I don't think this case really can happen */
3453                     SendToICS(ics_prefix);
3454                     SendToICS("refresh\n");
3455                 }
3456                 continue;
3457             }
3458
3459             /* Error messages */
3460 //          if (ics_user_moved) {
3461             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3462                 if (looking_at(buf, &i, "Illegal move") ||
3463                     looking_at(buf, &i, "Not a legal move") ||
3464                     looking_at(buf, &i, "Your king is in check") ||
3465                     looking_at(buf, &i, "It isn't your turn") ||
3466                     looking_at(buf, &i, "It is not your move")) {
3467                     /* Illegal move */
3468                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3469                         currentMove = forwardMostMove-1;
3470                         DisplayMove(currentMove - 1); /* before DMError */
3471                         DrawPosition(FALSE, boards[currentMove]);
3472                         SwitchClocks(forwardMostMove-1); // [HGM] race
3473                         DisplayBothClocks();
3474                     }
3475                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3476                     ics_user_moved = 0;
3477                     continue;
3478                 }
3479             }
3480
3481             if (looking_at(buf, &i, "still have time") ||
3482                 looking_at(buf, &i, "not out of time") ||
3483                 looking_at(buf, &i, "either player is out of time") ||
3484                 looking_at(buf, &i, "has timeseal; checking")) {
3485                 /* We must have called his flag a little too soon */
3486                 whiteFlag = blackFlag = FALSE;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "added * seconds to") ||
3491                 looking_at(buf, &i, "seconds were added to")) {
3492                 /* Update the clocks */
3493                 SendToICS(ics_prefix);
3494                 SendToICS("refresh\n");
3495                 continue;
3496             }
3497
3498             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3499                 ics_clock_paused = TRUE;
3500                 StopClocks();
3501                 continue;
3502             }
3503
3504             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3505                 ics_clock_paused = FALSE;
3506                 StartClocks();
3507                 continue;
3508             }
3509
3510             /* Grab player ratings from the Creating: message.
3511                Note we have to check for the special case when
3512                the ICS inserts things like [white] or [black]. */
3513             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3514                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3515                 /* star_matches:
3516                    0    player 1 name (not necessarily white)
3517                    1    player 1 rating
3518                    2    empty, white, or black (IGNORED)
3519                    3    player 2 name (not necessarily black)
3520                    4    player 2 rating
3521
3522                    The names/ratings are sorted out when the game
3523                    actually starts (below).
3524                 */
3525                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3526                 player1Rating = string_to_rating(star_match[1]);
3527                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3528                 player2Rating = string_to_rating(star_match[4]);
3529
3530                 if (appData.debugMode)
3531                   fprintf(debugFP,
3532                           "Ratings from 'Creating:' %s %d, %s %d\n",
3533                           player1Name, player1Rating,
3534                           player2Name, player2Rating);
3535
3536                 continue;
3537             }
3538
3539             /* Improved generic start/end-of-game messages */
3540             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3541                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3542                 /* If tkind == 0: */
3543                 /* star_match[0] is the game number */
3544                 /*           [1] is the white player's name */
3545                 /*           [2] is the black player's name */
3546                 /* For end-of-game: */
3547                 /*           [3] is the reason for the game end */
3548                 /*           [4] is a PGN end game-token, preceded by " " */
3549                 /* For start-of-game: */
3550                 /*           [3] begins with "Creating" or "Continuing" */
3551                 /*           [4] is " *" or empty (don't care). */
3552                 int gamenum = atoi(star_match[0]);
3553                 char *whitename, *blackname, *why, *endtoken;
3554                 ChessMove endtype = EndOfFile;
3555
3556                 if (tkind == 0) {
3557                   whitename = star_match[1];
3558                   blackname = star_match[2];
3559                   why = star_match[3];
3560                   endtoken = star_match[4];
3561                 } else {
3562                   whitename = star_match[1];
3563                   blackname = star_match[3];
3564                   why = star_match[5];
3565                   endtoken = star_match[6];
3566                 }
3567
3568                 /* Game start messages */
3569                 if (strncmp(why, "Creating ", 9) == 0 ||
3570                     strncmp(why, "Continuing ", 11) == 0) {
3571                     gs_gamenum = gamenum;
3572                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3573                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3574 #if ZIPPY
3575                     if (appData.zippyPlay) {
3576                         ZippyGameStart(whitename, blackname);
3577                     }
3578 #endif /*ZIPPY*/
3579                     partnerBoardValid = FALSE; // [HGM] bughouse
3580                     continue;
3581                 }
3582
3583                 /* Game end messages */
3584                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3585                     ics_gamenum != gamenum) {
3586                     continue;
3587                 }
3588                 while (endtoken[0] == ' ') endtoken++;
3589                 switch (endtoken[0]) {
3590                   case '*':
3591                   default:
3592                     endtype = GameUnfinished;
3593                     break;
3594                   case '0':
3595                     endtype = BlackWins;
3596                     break;
3597                   case '1':
3598                     if (endtoken[1] == '/')
3599                       endtype = GameIsDrawn;
3600                     else
3601                       endtype = WhiteWins;
3602                     break;
3603                 }
3604                 GameEnds(endtype, why, GE_ICS);
3605 #if ZIPPY
3606                 if (appData.zippyPlay && first.initDone) {
3607                     ZippyGameEnd(endtype, why);
3608                     if (first.pr == NULL) {
3609                       /* Start the next process early so that we'll
3610                          be ready for the next challenge */
3611                       StartChessProgram(&first);
3612                     }
3613                     /* Send "new" early, in case this command takes
3614                        a long time to finish, so that we'll be ready
3615                        for the next challenge. */
3616                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3617                     Reset(TRUE, TRUE);
3618                 }
3619 #endif /*ZIPPY*/
3620                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "Removing game * from observation") ||
3625                 looking_at(buf, &i, "no longer observing game *") ||
3626                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3627                 if (gameMode == IcsObserving &&
3628                     atoi(star_match[0]) == ics_gamenum)
3629                   {
3630                       /* icsEngineAnalyze */
3631                       if (appData.icsEngineAnalyze) {
3632                             ExitAnalyzeMode();
3633                             ModeHighlight();
3634                       }
3635                       StopClocks();
3636                       gameMode = IcsIdle;
3637                       ics_gamenum = -1;
3638                       ics_user_moved = FALSE;
3639                   }
3640                 continue;
3641             }
3642
3643             if (looking_at(buf, &i, "no longer examining game *")) {
3644                 if (gameMode == IcsExamining &&
3645                     atoi(star_match[0]) == ics_gamenum)
3646                   {
3647                       gameMode = IcsIdle;
3648                       ics_gamenum = -1;
3649                       ics_user_moved = FALSE;
3650                   }
3651                 continue;
3652             }
3653
3654             /* Advance leftover_start past any newlines we find,
3655                so only partial lines can get reparsed */
3656             if (looking_at(buf, &i, "\n")) {
3657                 prevColor = curColor;
3658                 if (curColor != ColorNormal) {
3659                     if (oldi > next_out) {
3660                         SendToPlayer(&buf[next_out], oldi - next_out);
3661                         next_out = oldi;
3662                     }
3663                     Colorize(ColorNormal, FALSE);
3664                     curColor = ColorNormal;
3665                 }
3666                 if (started == STARTED_BOARD) {
3667                     started = STARTED_NONE;
3668                     parse[parse_pos] = NULLCHAR;
3669                     ParseBoard12(parse);
3670                     ics_user_moved = 0;
3671
3672                     /* Send premove here */
3673                     if (appData.premove) {
3674                       char str[MSG_SIZ];
3675                       if (currentMove == 0 &&
3676                           gameMode == IcsPlayingWhite &&
3677                           appData.premoveWhite) {
3678                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                         SendToICS(str);
3682                       } else if (currentMove == 1 &&
3683                                  gameMode == IcsPlayingBlack &&
3684                                  appData.premoveBlack) {
3685                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3686                         if (appData.debugMode)
3687                           fprintf(debugFP, "Sending premove:\n");
3688                         SendToICS(str);
3689                       } else if (gotPremove) {
3690                         gotPremove = 0;
3691                         ClearPremoveHighlights();
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                           UserMoveEvent(premoveFromX, premoveFromY,
3695                                         premoveToX, premoveToY,
3696                                         premovePromoChar);
3697                       }
3698                     }
3699
3700                     /* Usually suppress following prompt */
3701                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3702                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3703                         if (looking_at(buf, &i, "*% ")) {
3704                             savingComment = FALSE;
3705                             suppressKibitz = 0;
3706                         }
3707                     }
3708                     next_out = i;
3709                 } else if (started == STARTED_HOLDINGS) {
3710                     int gamenum;
3711                     char new_piece[MSG_SIZ];
3712                     started = STARTED_NONE;
3713                     parse[parse_pos] = NULLCHAR;
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3716                                                         parse, currentMove);
3717                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3718                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3719                         if (gameInfo.variant == VariantNormal) {
3720                           /* [HGM] We seem to switch variant during a game!
3721                            * Presumably no holdings were displayed, so we have
3722                            * to move the position two files to the right to
3723                            * create room for them!
3724                            */
3725                           VariantClass newVariant;
3726                           switch(gameInfo.boardWidth) { // base guess on board width
3727                                 case 9:  newVariant = VariantShogi; break;
3728                                 case 10: newVariant = VariantGreat; break;
3729                                 default: newVariant = VariantCrazyhouse; break;
3730                           }
3731                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3732                           /* Get a move list just to see the header, which
3733                              will tell us whether this is really bug or zh */
3734                           if (ics_getting_history == H_FALSE) {
3735                             ics_getting_history = H_REQUESTED;
3736                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3737                             SendToICS(str);
3738                           }
3739                         }
3740                         new_piece[0] = NULLCHAR;
3741                         sscanf(parse, "game %d white [%s black [%s <- %s",
3742                                &gamenum, white_holding, black_holding,
3743                                new_piece);
3744                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3745                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3746                         /* [HGM] copy holdings to board holdings area */
3747                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3748                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3749                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3750 #if ZIPPY
3751                         if (appData.zippyPlay && first.initDone) {
3752                             ZippyHoldings(white_holding, black_holding,
3753                                           new_piece);
3754                         }
3755 #endif /*ZIPPY*/
3756                         if (tinyLayout || smallLayout) {
3757                             char wh[16], bh[16];
3758                             PackHolding(wh, white_holding);
3759                             PackHolding(bh, black_holding);
3760                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3761                                     gameInfo.white, gameInfo.black);
3762                         } else {
3763                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3764                                     gameInfo.white, white_holding,
3765                                     gameInfo.black, black_holding);
3766                         }
3767                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3768                         DrawPosition(FALSE, boards[currentMove]);
3769                         DisplayTitle(str);
3770                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3771                         sscanf(parse, "game %d white [%s black [%s <- %s",
3772                                &gamenum, white_holding, black_holding,
3773                                new_piece);
3774                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3775                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3776                         /* [HGM] copy holdings to partner-board holdings area */
3777                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3778                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3779                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3780                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3781                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3782                       }
3783                     }
3784                     /* Suppress following prompt */
3785                     if (looking_at(buf, &i, "*% ")) {
3786                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3787                         savingComment = FALSE;
3788                         suppressKibitz = 0;
3789                     }
3790                     next_out = i;
3791                 }
3792                 continue;
3793             }
3794
3795             i++;                /* skip unparsed character and loop back */
3796         }
3797
3798         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3799 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3800 //          SendToPlayer(&buf[next_out], i - next_out);
3801             started != STARTED_HOLDINGS && leftover_start > next_out) {
3802             SendToPlayer(&buf[next_out], leftover_start - next_out);
3803             next_out = i;
3804         }
3805
3806         leftover_len = buf_len - leftover_start;
3807         /* if buffer ends with something we couldn't parse,
3808            reparse it after appending the next read */
3809
3810     } else if (count == 0) {
3811         RemoveInputSource(isr);
3812         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3813     } else {
3814         DisplayFatalError(_("Error reading from ICS"), error, 1);
3815     }
3816 }
3817
3818
3819 /* Board style 12 looks like this:
3820
3821    <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
3822
3823  * The "<12> " is stripped before it gets to this routine.  The two
3824  * trailing 0's (flip state and clock ticking) are later addition, and
3825  * some chess servers may not have them, or may have only the first.
3826  * Additional trailing fields may be added in the future.
3827  */
3828
3829 #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"
3830
3831 #define RELATION_OBSERVING_PLAYED    0
3832 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3833 #define RELATION_PLAYING_MYMOVE      1
3834 #define RELATION_PLAYING_NOTMYMOVE  -1
3835 #define RELATION_EXAMINING           2
3836 #define RELATION_ISOLATED_BOARD     -3
3837 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3838
3839 void
3840 ParseBoard12(string)
3841      char *string;
3842 {
3843     GameMode newGameMode;
3844     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3845     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3846     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3847     char to_play, board_chars[200];
3848     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3849     char black[32], white[32];
3850     Board board;
3851     int prevMove = currentMove;
3852     int ticking = 2;
3853     ChessMove moveType;
3854     int fromX, fromY, toX, toY;
3855     char promoChar;
3856     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3857     char *bookHit = NULL; // [HGM] book
3858     Boolean weird = FALSE, reqFlag = FALSE;
3859
3860     fromX = fromY = toX = toY = -1;
3861
3862     newGame = FALSE;
3863
3864     if (appData.debugMode)
3865       fprintf(debugFP, _("Parsing board: %s\n"), string);
3866
3867     move_str[0] = NULLCHAR;
3868     elapsed_time[0] = NULLCHAR;
3869     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3870         int  i = 0, j;
3871         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3872             if(string[i] == ' ') { ranks++; files = 0; }
3873             else files++;
3874             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3875             i++;
3876         }
3877         for(j = 0; j <i; j++) board_chars[j] = string[j];
3878         board_chars[i] = '\0';
3879         string += i + 1;
3880     }
3881     n = sscanf(string, PATTERN, &to_play, &double_push,
3882                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3883                &gamenum, white, black, &relation, &basetime, &increment,
3884                &white_stren, &black_stren, &white_time, &black_time,
3885                &moveNum, str, elapsed_time, move_str, &ics_flip,
3886                &ticking);
3887
3888     if (n < 21) {
3889         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3890         DisplayError(str, 0);
3891         return;
3892     }
3893
3894     /* Convert the move number to internal form */
3895     moveNum = (moveNum - 1) * 2;
3896     if (to_play == 'B') moveNum++;
3897     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3899                         0, 1);
3900       return;
3901     }
3902
3903     switch (relation) {
3904       case RELATION_OBSERVING_PLAYED:
3905       case RELATION_OBSERVING_STATIC:
3906         if (gamenum == -1) {
3907             /* Old ICC buglet */
3908             relation = RELATION_OBSERVING_STATIC;
3909         }
3910         newGameMode = IcsObserving;
3911         break;
3912       case RELATION_PLAYING_MYMOVE:
3913       case RELATION_PLAYING_NOTMYMOVE:
3914         newGameMode =
3915           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3916             IcsPlayingWhite : IcsPlayingBlack;
3917         break;
3918       case RELATION_EXAMINING:
3919         newGameMode = IcsExamining;
3920         break;
3921       case RELATION_ISOLATED_BOARD:
3922       default:
3923         /* Just display this board.  If user was doing something else,
3924            we will forget about it until the next board comes. */
3925         newGameMode = IcsIdle;
3926         break;
3927       case RELATION_STARTING_POSITION:
3928         newGameMode = gameMode;
3929         break;
3930     }
3931
3932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3933          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3934       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3935       char *toSqr;
3936       for (k = 0; k < ranks; k++) {
3937         for (j = 0; j < files; j++)
3938           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3939         if(gameInfo.holdingsWidth > 1) {
3940              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3941              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3942         }
3943       }
3944       CopyBoard(partnerBoard, board);
3945       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3946         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3947         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3948       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3949       if(toSqr = strchr(str, '-')) {
3950         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3951         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3952       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3953       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3954       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3955       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3956       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3957       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3958                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3959       DisplayMessage(partnerStatus, "");
3960         partnerBoardValid = TRUE;
3961       return;
3962     }
3963
3964     /* Modify behavior for initial board display on move listing
3965        of wild games.
3966        */
3967     switch (ics_getting_history) {
3968       case H_FALSE:
3969       case H_REQUESTED:
3970         break;
3971       case H_GOT_REQ_HEADER:
3972       case H_GOT_UNREQ_HEADER:
3973         /* This is the initial position of the current game */
3974         gamenum = ics_gamenum;
3975         moveNum = 0;            /* old ICS bug workaround */
3976         if (to_play == 'B') {
3977           startedFromSetupPosition = TRUE;
3978           blackPlaysFirst = TRUE;
3979           moveNum = 1;
3980           if (forwardMostMove == 0) forwardMostMove = 1;
3981           if (backwardMostMove == 0) backwardMostMove = 1;
3982           if (currentMove == 0) currentMove = 1;
3983         }
3984         newGameMode = gameMode;
3985         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3986         break;
3987       case H_GOT_UNWANTED_HEADER:
3988         /* This is an initial board that we don't want */
3989         return;
3990       case H_GETTING_MOVES:
3991         /* Should not happen */
3992         DisplayError(_("Error gathering move list: extra board"), 0);
3993         ics_getting_history = H_FALSE;
3994         return;
3995     }
3996
3997    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3998                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3999      /* [HGM] We seem to have switched variant unexpectedly
4000       * Try to guess new variant from board size
4001       */
4002           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4003           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4004           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4005           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4006           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4007           if(!weird) newVariant = VariantNormal;
4008           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4009           /* Get a move list just to see the header, which
4010              will tell us whether this is really bug or zh */
4011           if (ics_getting_history == H_FALSE) {
4012             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4013             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4014             SendToICS(str);
4015           }
4016     }
4017
4018     /* Take action if this is the first board of a new game, or of a
4019        different game than is currently being displayed.  */
4020     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4021         relation == RELATION_ISOLATED_BOARD) {
4022
4023         /* Forget the old game and get the history (if any) of the new one */
4024         if (gameMode != BeginningOfGame) {
4025           Reset(TRUE, TRUE);
4026         }
4027         newGame = TRUE;
4028         if (appData.autoRaiseBoard) BoardToTop();
4029         prevMove = -3;
4030         if (gamenum == -1) {
4031             newGameMode = IcsIdle;
4032         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4033                    appData.getMoveList && !reqFlag) {
4034             /* Need to get game history */
4035             ics_getting_history = H_REQUESTED;
4036             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037             SendToICS(str);
4038         }
4039
4040         /* Initially flip the board to have black on the bottom if playing
4041            black or if the ICS flip flag is set, but let the user change
4042            it with the Flip View button. */
4043         flipView = appData.autoFlipView ?
4044           (newGameMode == IcsPlayingBlack) || ics_flip :
4045           appData.flipView;
4046
4047         /* Done with values from previous mode; copy in new ones */
4048         gameMode = newGameMode;
4049         ModeHighlight();
4050         ics_gamenum = gamenum;
4051         if (gamenum == gs_gamenum) {
4052             int klen = strlen(gs_kind);
4053             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4054             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4055             gameInfo.event = StrSave(str);
4056         } else {
4057             gameInfo.event = StrSave("ICS game");
4058         }
4059         gameInfo.site = StrSave(appData.icsHost);
4060         gameInfo.date = PGNDate();
4061         gameInfo.round = StrSave("-");
4062         gameInfo.white = StrSave(white);
4063         gameInfo.black = StrSave(black);
4064         timeControl = basetime * 60 * 1000;
4065         timeControl_2 = 0;
4066         timeIncrement = increment * 1000;
4067         movesPerSession = 0;
4068         gameInfo.timeControl = TimeControlTagValue();
4069         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4070   if (appData.debugMode) {
4071     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4072     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4073     setbuf(debugFP, NULL);
4074   }
4075
4076         gameInfo.outOfBook = NULL;
4077
4078         /* Do we have the ratings? */
4079         if (strcmp(player1Name, white) == 0 &&
4080             strcmp(player2Name, black) == 0) {
4081             if (appData.debugMode)
4082               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4083                       player1Rating, player2Rating);
4084             gameInfo.whiteRating = player1Rating;
4085             gameInfo.blackRating = player2Rating;
4086         } else if (strcmp(player2Name, white) == 0 &&
4087                    strcmp(player1Name, black) == 0) {
4088             if (appData.debugMode)
4089               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4090                       player2Rating, player1Rating);
4091             gameInfo.whiteRating = player2Rating;
4092             gameInfo.blackRating = player1Rating;
4093         }
4094         player1Name[0] = player2Name[0] = NULLCHAR;
4095
4096         /* Silence shouts if requested */
4097         if (appData.quietPlay &&
4098             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4099             SendToICS(ics_prefix);
4100             SendToICS("set shout 0\n");
4101         }
4102     }
4103
4104     /* Deal with midgame name changes */
4105     if (!newGame) {
4106         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4107             if (gameInfo.white) free(gameInfo.white);
4108             gameInfo.white = StrSave(white);
4109         }
4110         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4111             if (gameInfo.black) free(gameInfo.black);
4112             gameInfo.black = StrSave(black);
4113         }
4114     }
4115
4116     /* Throw away game result if anything actually changes in examine mode */
4117     if (gameMode == IcsExamining && !newGame) {
4118         gameInfo.result = GameUnfinished;
4119         if (gameInfo.resultDetails != NULL) {
4120             free(gameInfo.resultDetails);
4121             gameInfo.resultDetails = NULL;
4122         }
4123     }
4124
4125     /* In pausing && IcsExamining mode, we ignore boards coming
4126        in if they are in a different variation than we are. */
4127     if (pauseExamInvalid) return;
4128     if (pausing && gameMode == IcsExamining) {
4129         if (moveNum <= pauseExamForwardMostMove) {
4130             pauseExamInvalid = TRUE;
4131             forwardMostMove = pauseExamForwardMostMove;
4132             return;
4133         }
4134     }
4135
4136   if (appData.debugMode) {
4137     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4138   }
4139     /* Parse the board */
4140     for (k = 0; k < ranks; k++) {
4141       for (j = 0; j < files; j++)
4142         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143       if(gameInfo.holdingsWidth > 1) {
4144            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146       }
4147     }
4148     CopyBoard(boards[moveNum], board);
4149     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4150     if (moveNum == 0) {
4151         startedFromSetupPosition =
4152           !CompareBoards(board, initialPosition);
4153         if(startedFromSetupPosition)
4154             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4155     }
4156
4157     /* [HGM] Set castling rights. Take the outermost Rooks,
4158        to make it also work for FRC opening positions. Note that board12
4159        is really defective for later FRC positions, as it has no way to
4160        indicate which Rook can castle if they are on the same side of King.
4161        For the initial position we grant rights to the outermost Rooks,
4162        and remember thos rights, and we then copy them on positions
4163        later in an FRC game. This means WB might not recognize castlings with
4164        Rooks that have moved back to their original position as illegal,
4165        but in ICS mode that is not its job anyway.
4166     */
4167     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4168     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4169
4170         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4171             if(board[0][i] == WhiteRook) j = i;
4172         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4173         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4177             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4178         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182
4183         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[BOARD_HEIGHT-1][k] == bKing)
4188                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4189         if(gameInfo.variant == VariantTwoKings) {
4190             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4191             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4192             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4193         }
4194     } else { int r;
4195         r = boards[moveNum][CASTLING][0] = initialRights[0];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4197         r = boards[moveNum][CASTLING][1] = initialRights[1];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4199         r = boards[moveNum][CASTLING][3] = initialRights[3];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4201         r = boards[moveNum][CASTLING][4] = initialRights[4];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4203         /* wildcastle kludge: always assume King has rights */
4204         r = boards[moveNum][CASTLING][2] = initialRights[2];
4205         r = boards[moveNum][CASTLING][5] = initialRights[5];
4206     }
4207     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4208     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4209
4210
4211     if (ics_getting_history == H_GOT_REQ_HEADER ||
4212         ics_getting_history == H_GOT_UNREQ_HEADER) {
4213         /* This was an initial position from a move list, not
4214            the current position */
4215         return;
4216     }
4217
4218     /* Update currentMove and known move number limits */
4219     newMove = newGame || moveNum > forwardMostMove;
4220
4221     if (newGame) {
4222         forwardMostMove = backwardMostMove = currentMove = moveNum;
4223         if (gameMode == IcsExamining && moveNum == 0) {
4224           /* Workaround for ICS limitation: we are not told the wild
4225              type when starting to examine a game.  But if we ask for
4226              the move list, the move list header will tell us */
4227             ics_getting_history = H_REQUESTED;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230         }
4231     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4232                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4233 #if ZIPPY
4234         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4235         /* [HGM] applied this also to an engine that is silently watching        */
4236         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4237             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4238             gameInfo.variant == currentlyInitializedVariant) {
4239           takeback = forwardMostMove - moveNum;
4240           for (i = 0; i < takeback; i++) {
4241             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4242             SendToProgram("undo\n", &first);
4243           }
4244         }
4245 #endif
4246
4247         forwardMostMove = moveNum;
4248         if (!pausing || currentMove > forwardMostMove)
4249           currentMove = forwardMostMove;
4250     } else {
4251         /* New part of history that is not contiguous with old part */
4252         if (pausing && gameMode == IcsExamining) {
4253             pauseExamInvalid = TRUE;
4254             forwardMostMove = pauseExamForwardMostMove;
4255             return;
4256         }
4257         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4258 #if ZIPPY
4259             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4260                 // [HGM] when we will receive the move list we now request, it will be
4261                 // fed to the engine from the first move on. So if the engine is not
4262                 // in the initial position now, bring it there.
4263                 InitChessProgram(&first, 0);
4264             }
4265 #endif
4266             ics_getting_history = H_REQUESTED;
4267             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4268             SendToICS(str);
4269         }
4270         forwardMostMove = backwardMostMove = currentMove = moveNum;
4271     }
4272
4273     /* Update the clocks */
4274     if (strchr(elapsed_time, '.')) {
4275       /* Time is in ms */
4276       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4277       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4278     } else {
4279       /* Time is in seconds */
4280       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4281       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4282     }
4283
4284
4285 #if ZIPPY
4286     if (appData.zippyPlay && newGame &&
4287         gameMode != IcsObserving && gameMode != IcsIdle &&
4288         gameMode != IcsExamining)
4289       ZippyFirstBoard(moveNum, basetime, increment);
4290 #endif
4291
4292     /* Put the move on the move list, first converting
4293        to canonical algebraic form. */
4294     if (moveNum > 0) {
4295   if (appData.debugMode) {
4296     if (appData.debugMode) { int f = forwardMostMove;
4297         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4298                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4299                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4300     }
4301     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4302     fprintf(debugFP, "moveNum = %d\n", moveNum);
4303     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4304     setbuf(debugFP, NULL);
4305   }
4306         if (moveNum <= backwardMostMove) {
4307             /* We don't know what the board looked like before
4308                this move.  Punt. */
4309           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4310             strcat(parseList[moveNum - 1], " ");
4311             strcat(parseList[moveNum - 1], elapsed_time);
4312             moveList[moveNum - 1][0] = NULLCHAR;
4313         } else if (strcmp(move_str, "none") == 0) {
4314             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4315             /* Again, we don't know what the board looked like;
4316                this is really the start of the game. */
4317             parseList[moveNum - 1][0] = NULLCHAR;
4318             moveList[moveNum - 1][0] = NULLCHAR;
4319             backwardMostMove = moveNum;
4320             startedFromSetupPosition = TRUE;
4321             fromX = fromY = toX = toY = -1;
4322         } else {
4323           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4324           //                 So we parse the long-algebraic move string in stead of the SAN move
4325           int valid; char buf[MSG_SIZ], *prom;
4326
4327           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4328                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4329           // str looks something like "Q/a1-a2"; kill the slash
4330           if(str[1] == '/')
4331             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4332           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4333           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4334                 strcat(buf, prom); // long move lacks promo specification!
4335           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4336                 if(appData.debugMode)
4337                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4338                 safeStrCpy(move_str, buf, MSG_SIZ);
4339           }
4340           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4341                                 &fromX, &fromY, &toX, &toY, &promoChar)
4342                || ParseOneMove(buf, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar);
4344           // end of long SAN patch
4345           if (valid) {
4346             (void) CoordsToAlgebraic(boards[moveNum - 1],
4347                                      PosFlags(moveNum - 1),
4348                                      fromY, fromX, toY, toX, promoChar,
4349                                      parseList[moveNum-1]);
4350             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4351               case MT_NONE:
4352               case MT_STALEMATE:
4353               default:
4354                 break;
4355               case MT_CHECK:
4356                 if(gameInfo.variant != VariantShogi)
4357                     strcat(parseList[moveNum - 1], "+");
4358                 break;
4359               case MT_CHECKMATE:
4360               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4361                 strcat(parseList[moveNum - 1], "#");
4362                 break;
4363             }
4364             strcat(parseList[moveNum - 1], " ");
4365             strcat(parseList[moveNum - 1], elapsed_time);
4366             /* currentMoveString is set as a side-effect of ParseOneMove */
4367             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4368             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4369             strcat(moveList[moveNum - 1], "\n");
4370
4371             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 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);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
5989         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5990             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5991             /* User is moving for Black */
5992             if (WhiteOnMove(currentMove)) {
5993                 DisplayMoveError(_("It is White's turn"));
5994                 return;
5995             }
5996         } else {
5997             /* User is moving for White */
5998             if (!WhiteOnMove(currentMove)) {
5999                 DisplayMoveError(_("It is Black's turn"));
6000                 return;
6001             }
6002         }
6003         break;
6004
6005       case IcsPlayingBlack:
6006         /* User is moving for Black */
6007         if (WhiteOnMove(currentMove)) {
6008             if (!appData.premove) {
6009                 DisplayMoveError(_("It is White's turn"));
6010             } else if (toX >= 0 && toY >= 0) {
6011                 premoveToX = toX;
6012                 premoveToY = toY;
6013                 premoveFromX = fromX;
6014                 premoveFromY = fromY;
6015                 premovePromoChar = promoChar;
6016                 gotPremove = 1;
6017                 if (appData.debugMode)
6018                     fprintf(debugFP, "Got premove: fromX %d,"
6019                             "fromY %d, toX %d, toY %d\n",
6020                             fromX, fromY, toX, toY);
6021             }
6022             return;
6023         }
6024         break;
6025
6026       case IcsPlayingWhite:
6027         /* User is moving for White */
6028         if (!WhiteOnMove(currentMove)) {
6029             if (!appData.premove) {
6030                 DisplayMoveError(_("It is Black's turn"));
6031             } else if (toX >= 0 && toY >= 0) {
6032                 premoveToX = toX;
6033                 premoveToY = toY;
6034                 premoveFromX = fromX;
6035                 premoveFromY = fromY;
6036                 premovePromoChar = promoChar;
6037                 gotPremove = 1;
6038                 if (appData.debugMode)
6039                     fprintf(debugFP, "Got premove: fromX %d,"
6040                             "fromY %d, toX %d, toY %d\n",
6041                             fromX, fromY, toX, toY);
6042             }
6043             return;
6044         }
6045         break;
6046
6047       default:
6048         break;
6049
6050       case EditPosition:
6051         /* EditPosition, empty square, or different color piece;
6052            click-click move is possible */
6053         if (toX == -2 || toY == -2) {
6054             boards[0][fromY][fromX] = EmptySquare;
6055             DrawPosition(FALSE, boards[currentMove]);
6056             return;
6057         } else if (toX >= 0 && toY >= 0) {
6058             boards[0][toY][toX] = boards[0][fromY][fromX];
6059             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6060                 if(boards[0][fromY][0] != EmptySquare) {
6061                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6062                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6063                 }
6064             } else
6065             if(fromX == BOARD_RGHT+1) {
6066                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6067                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6068                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6069                 }
6070             } else
6071             boards[0][fromY][fromX] = EmptySquare;
6072             DrawPosition(FALSE, boards[currentMove]);
6073             return;
6074         }
6075         return;
6076     }
6077
6078     if(toX < 0 || toY < 0) return;
6079     pdown = boards[currentMove][fromY][fromX];
6080     pup = boards[currentMove][toY][toX];
6081
6082     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6083     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6084          if( pup != EmptySquare ) return;
6085          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6086            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6087                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6088            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6089            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6090            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6091            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6092          fromY = DROP_RANK;
6093     }
6094
6095     /* [HGM] always test for legality, to get promotion info */
6096     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6097                                          fromY, fromX, toY, toX, promoChar);
6098     /* [HGM] but possibly ignore an IllegalMove result */
6099     if (appData.testLegality) {
6100         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6101             DisplayMoveError(_("Illegal move"));
6102             return;
6103         }
6104     }
6105
6106     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6107 }
6108
6109 /* Common tail of UserMoveEvent and DropMenuEvent */
6110 int
6111 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6112      ChessMove moveType;
6113      int fromX, fromY, toX, toY;
6114      /*char*/int promoChar;
6115 {
6116     char *bookHit = 0;
6117
6118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6119         // [HGM] superchess: suppress promotions to non-available piece
6120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6121         if(WhiteOnMove(currentMove)) {
6122             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6123         } else {
6124             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6125         }
6126     }
6127
6128     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6129        move type in caller when we know the move is a legal promotion */
6130     if(moveType == NormalMove && promoChar)
6131         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6132
6133     /* [HGM] <popupFix> The following if has been moved here from
6134        UserMoveEvent(). Because it seemed to belong here (why not allow
6135        piece drops in training games?), and because it can only be
6136        performed after it is known to what we promote. */
6137     if (gameMode == Training) {
6138       /* compare the move played on the board to the next move in the
6139        * game. If they match, display the move and the opponent's response.
6140        * If they don't match, display an error message.
6141        */
6142       int saveAnimate;
6143       Board testBoard;
6144       CopyBoard(testBoard, boards[currentMove]);
6145       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6146
6147       if (CompareBoards(testBoard, boards[currentMove+1])) {
6148         ForwardInner(currentMove+1);
6149
6150         /* Autoplay the opponent's response.
6151          * if appData.animate was TRUE when Training mode was entered,
6152          * the response will be animated.
6153          */
6154         saveAnimate = appData.animate;
6155         appData.animate = animateTraining;
6156         ForwardInner(currentMove+1);
6157         appData.animate = saveAnimate;
6158
6159         /* check for the end of the game */
6160         if (currentMove >= forwardMostMove) {
6161           gameMode = PlayFromGameFile;
6162           ModeHighlight();
6163           SetTrainingModeOff();
6164           DisplayInformation(_("End of game"));
6165         }
6166       } else {
6167         DisplayError(_("Incorrect move"), 0);
6168       }
6169       return 1;
6170     }
6171
6172   /* Ok, now we know that the move is good, so we can kill
6173      the previous line in Analysis Mode */
6174   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6175                                 && currentMove < forwardMostMove) {
6176     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6177     else forwardMostMove = currentMove;
6178   }
6179
6180   /* If we need the chess program but it's dead, restart it */
6181   ResurrectChessProgram();
6182
6183   /* A user move restarts a paused game*/
6184   if (pausing)
6185     PauseEvent();
6186
6187   thinkOutput[0] = NULLCHAR;
6188
6189   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6190
6191   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6192
6193   if (gameMode == BeginningOfGame) {
6194     if (appData.noChessProgram) {
6195       gameMode = EditGame;
6196       SetGameInfo();
6197     } else {
6198       char buf[MSG_SIZ];
6199       gameMode = MachinePlaysBlack;
6200       StartClocks();
6201       SetGameInfo();
6202       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6203       DisplayTitle(buf);
6204       if (first.sendName) {
6205         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6206         SendToProgram(buf, &first);
6207       }
6208       StartClocks();
6209     }
6210     ModeHighlight();
6211   }
6212
6213   /* Relay move to ICS or chess engine */
6214   if (appData.icsActive) {
6215     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6216         gameMode == IcsExamining) {
6217       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6218         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6219         SendToICS("draw ");
6220         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6221       }
6222       // also send plain move, in case ICS does not understand atomic claims
6223       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6224       ics_user_moved = 1;
6225     }
6226   } else {
6227     if (first.sendTime && (gameMode == BeginningOfGame ||
6228                            gameMode == MachinePlaysWhite ||
6229                            gameMode == MachinePlaysBlack)) {
6230       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6231     }
6232     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6233          // [HGM] book: if program might be playing, let it use book
6234         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6235         first.maybeThinking = TRUE;
6236     } else SendMoveToProgram(forwardMostMove-1, &first);
6237     if (currentMove == cmailOldMove + 1) {
6238       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6239     }
6240   }
6241
6242   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6243
6244   switch (gameMode) {
6245   case EditGame:
6246     if(appData.testLegality)
6247     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6248     case MT_NONE:
6249     case MT_CHECK:
6250       break;
6251     case MT_CHECKMATE:
6252     case MT_STAINMATE:
6253       if (WhiteOnMove(currentMove)) {
6254         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6255       } else {
6256         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6257       }
6258       break;
6259     case MT_STALEMATE:
6260       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6261       break;
6262     }
6263     break;
6264
6265   case MachinePlaysBlack:
6266   case MachinePlaysWhite:
6267     /* disable certain menu options while machine is thinking */
6268     SetMachineThinkingEnables();
6269     break;
6270
6271   default:
6272     break;
6273   }
6274
6275   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6276
6277   if(bookHit) { // [HGM] book: simulate book reply
6278         static char bookMove[MSG_SIZ]; // a bit generous?
6279
6280         programStats.nodes = programStats.depth = programStats.time =
6281         programStats.score = programStats.got_only_move = 0;
6282         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6283
6284         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6285         strcat(bookMove, bookHit);
6286         HandleMachineMove(bookMove, &first);
6287   }
6288   return 1;
6289 }
6290
6291 void
6292 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6293      Board board;
6294      int flags;
6295      ChessMove kind;
6296      int rf, ff, rt, ft;
6297      VOIDSTAR closure;
6298 {
6299     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6300     Markers *m = (Markers *) closure;
6301     if(rf == fromY && ff == fromX)
6302         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6303                          || kind == WhiteCapturesEnPassant
6304                          || kind == BlackCapturesEnPassant);
6305     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6306 }
6307
6308 void
6309 MarkTargetSquares(int clear)
6310 {
6311   int x, y;
6312   if(!appData.markers || !appData.highlightDragging ||
6313      !appData.testLegality || gameMode == EditPosition) return;
6314   if(clear) {
6315     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6316   } else {
6317     int capt = 0;
6318     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6319     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6320       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6321       if(capt)
6322       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6323     }
6324   }
6325   DrawPosition(TRUE, NULL);
6326 }
6327
6328 int
6329 Explode(Board board, int fromX, int fromY, int toX, int toY)
6330 {
6331     if(gameInfo.variant == VariantAtomic &&
6332        (board[toY][toX] != EmptySquare ||                     // capture?
6333         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6334                          board[fromY][fromX] == BlackPawn   )
6335       )) {
6336         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6337         return TRUE;
6338     }
6339     return FALSE;
6340 }
6341
6342 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6343
6344 void LeftClick(ClickType clickType, int xPix, int yPix)
6345 {
6346     int x, y;
6347     Boolean saveAnimate;
6348     static int second = 0, promotionChoice = 0, dragging = 0;
6349     char promoChoice = NULLCHAR;
6350
6351     if(appData.seekGraph && appData.icsActive && loggedOn &&
6352         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6353         SeekGraphClick(clickType, xPix, yPix, 0);
6354         return;
6355     }
6356
6357     if (clickType == Press) ErrorPopDown();
6358     MarkTargetSquares(1);
6359
6360     x = EventToSquare(xPix, BOARD_WIDTH);
6361     y = EventToSquare(yPix, BOARD_HEIGHT);
6362     if (!flipView && y >= 0) {
6363         y = BOARD_HEIGHT - 1 - y;
6364     }
6365     if (flipView && x >= 0) {
6366         x = BOARD_WIDTH - 1 - x;
6367     }
6368
6369     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6370         if(clickType == Release) return; // ignore upclick of click-click destination
6371         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6372         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6373         if(gameInfo.holdingsWidth &&
6374                 (WhiteOnMove(currentMove)
6375                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6376                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6377             // click in right holdings, for determining promotion piece
6378             ChessSquare p = boards[currentMove][y][x];
6379             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6380             if(p != EmptySquare) {
6381                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6382                 fromX = fromY = -1;
6383                 return;
6384             }
6385         }
6386         DrawPosition(FALSE, boards[currentMove]);
6387         return;
6388     }
6389
6390     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6391     if(clickType == Press
6392             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6393               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6394               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6395         return;
6396
6397     autoQueen = appData.alwaysPromoteToQueen;
6398
6399     if (fromX == -1) {
6400       gatingPiece = EmptySquare;
6401       if (clickType != Press) {
6402         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6403             DragPieceEnd(xPix, yPix); dragging = 0;
6404             DrawPosition(FALSE, NULL);
6405         }
6406         return;
6407       }
6408       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6409             /* First square */
6410             if (OKToStartUserMove(x, y)) {
6411                 fromX = x;
6412                 fromY = y;
6413                 second = 0;
6414                 MarkTargetSquares(0);
6415                 DragPieceBegin(xPix, yPix); dragging = 1;
6416                 if (appData.highlightDragging) {
6417                     SetHighlights(x, y, -1, -1);
6418                 }
6419             }
6420             return;
6421         }
6422     }
6423
6424     /* fromX != -1 */
6425     if (clickType == Press && gameMode != EditPosition) {
6426         ChessSquare fromP;
6427         ChessSquare toP;
6428         int frc;
6429
6430         // ignore off-board to clicks
6431         if(y < 0 || x < 0) return;
6432
6433         /* Check if clicking again on the same color piece */
6434         fromP = boards[currentMove][fromY][fromX];
6435         toP = boards[currentMove][y][x];
6436         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6437         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6438              WhitePawn <= toP && toP <= WhiteKing &&
6439              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6440              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6441             (BlackPawn <= fromP && fromP <= BlackKing &&
6442              BlackPawn <= toP && toP <= BlackKing &&
6443              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6444              !(fromP == BlackKing && toP == BlackRook && frc))) {
6445             /* Clicked again on same color piece -- changed his mind */
6446             second = (x == fromX && y == fromY);
6447            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6448             if (appData.highlightDragging) {
6449                 SetHighlights(x, y, -1, -1);
6450             } else {
6451                 ClearHighlights();
6452             }
6453             if (OKToStartUserMove(x, y)) {
6454                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6455                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6456                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6457                  gatingPiece = boards[currentMove][fromY][fromX];
6458                 else gatingPiece = EmptySquare;
6459                 fromX = x;
6460                 fromY = y; dragging = 1;
6461                 MarkTargetSquares(0);
6462                 DragPieceBegin(xPix, yPix);
6463             }
6464            }
6465            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6466            second = FALSE; 
6467         }
6468         // ignore clicks on holdings
6469         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6470     }
6471
6472     if (clickType == Release && x == fromX && y == fromY) {
6473         DragPieceEnd(xPix, yPix); dragging = 0;
6474         if (appData.animateDragging) {
6475             /* Undo animation damage if any */
6476             DrawPosition(FALSE, NULL);
6477         }
6478         if (second) {
6479             /* Second up/down in same square; just abort move */
6480             second = 0;
6481             fromX = fromY = -1;
6482             gatingPiece = EmptySquare;
6483             ClearHighlights();
6484             gotPremove = 0;
6485             ClearPremoveHighlights();
6486         } else {
6487             /* First upclick in same square; start click-click mode */
6488             SetHighlights(x, y, -1, -1);
6489         }
6490         return;
6491     }
6492
6493     /* we now have a different from- and (possibly off-board) to-square */
6494     /* Completed move */
6495     toX = x;
6496     toY = y;
6497     saveAnimate = appData.animate;
6498     if (clickType == Press) {
6499         /* Finish clickclick move */
6500         if (appData.animate || appData.highlightLastMove) {
6501             SetHighlights(fromX, fromY, toX, toY);
6502         } else {
6503             ClearHighlights();
6504         }
6505     } else {
6506         /* Finish drag move */
6507         if (appData.highlightLastMove) {
6508             SetHighlights(fromX, fromY, toX, toY);
6509         } else {
6510             ClearHighlights();
6511         }
6512         DragPieceEnd(xPix, yPix); dragging = 0;
6513         /* Don't animate move and drag both */
6514         appData.animate = FALSE;
6515     }
6516
6517     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6518     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6519         ChessSquare piece = boards[currentMove][fromY][fromX];
6520         if(gameMode == EditPosition && piece != EmptySquare &&
6521            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6522             int n;
6523
6524             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6525                 n = PieceToNumber(piece - (int)BlackPawn);
6526                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6527                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6528                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6529             } else
6530             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6531                 n = PieceToNumber(piece);
6532                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6533                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6534                 boards[currentMove][n][BOARD_WIDTH-2]++;
6535             }
6536             boards[currentMove][fromY][fromX] = EmptySquare;
6537         }
6538         ClearHighlights();
6539         fromX = fromY = -1;
6540         DrawPosition(TRUE, boards[currentMove]);
6541         return;
6542     }
6543
6544     // off-board moves should not be highlighted
6545     if(x < 0 || y < 0) ClearHighlights();
6546
6547     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6548
6549     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6550         SetHighlights(fromX, fromY, toX, toY);
6551         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6552             // [HGM] super: promotion to captured piece selected from holdings
6553             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6554             promotionChoice = TRUE;
6555             // kludge follows to temporarily execute move on display, without promoting yet
6556             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6557             boards[currentMove][toY][toX] = p;
6558             DrawPosition(FALSE, boards[currentMove]);
6559             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6560             boards[currentMove][toY][toX] = q;
6561             DisplayMessage("Click in holdings to choose piece", "");
6562             return;
6563         }
6564         PromotionPopUp();
6565     } else {
6566         int oldMove = currentMove;
6567         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6568         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6569         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6570         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6571            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6572             DrawPosition(TRUE, boards[currentMove]);
6573         fromX = fromY = -1;
6574     }
6575     appData.animate = saveAnimate;
6576     if (appData.animate || appData.animateDragging) {
6577         /* Undo animation damage if needed */
6578         DrawPosition(FALSE, NULL);
6579     }
6580 }
6581
6582 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6583 {   // front-end-free part taken out of PieceMenuPopup
6584     int whichMenu; int xSqr, ySqr;
6585
6586     if(seekGraphUp) { // [HGM] seekgraph
6587         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6588         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6589         return -2;
6590     }
6591
6592     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6593          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6594         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6595         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6596         if(action == Press)   {
6597             originalFlip = flipView;
6598             flipView = !flipView; // temporarily flip board to see game from partners perspective
6599             DrawPosition(TRUE, partnerBoard);
6600             DisplayMessage(partnerStatus, "");
6601             partnerUp = TRUE;
6602         } else if(action == Release) {
6603             flipView = originalFlip;
6604             DrawPosition(TRUE, boards[currentMove]);
6605             partnerUp = FALSE;
6606         }
6607         return -2;
6608     }
6609
6610     xSqr = EventToSquare(x, BOARD_WIDTH);
6611     ySqr = EventToSquare(y, BOARD_HEIGHT);
6612     if (action == Release) UnLoadPV(); // [HGM] pv
6613     if (action != Press) return -2; // return code to be ignored
6614     switch (gameMode) {
6615       case IcsExamining:
6616         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6617       case EditPosition:
6618         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6619         if (xSqr < 0 || ySqr < 0) return -1;\r
6620         whichMenu = 0; // edit-position menu
6621         break;
6622       case IcsObserving:
6623         if(!appData.icsEngineAnalyze) return -1;
6624       case IcsPlayingWhite:
6625       case IcsPlayingBlack:
6626         if(!appData.zippyPlay) goto noZip;
6627       case AnalyzeMode:
6628       case AnalyzeFile:
6629       case MachinePlaysWhite:
6630       case MachinePlaysBlack:
6631       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6632         if (!appData.dropMenu) {
6633           LoadPV(x, y);
6634           return 2; // flag front-end to grab mouse events
6635         }
6636         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6637            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6638       case EditGame:
6639       noZip:
6640         if (xSqr < 0 || ySqr < 0) return -1;
6641         if (!appData.dropMenu || appData.testLegality &&
6642             gameInfo.variant != VariantBughouse &&
6643             gameInfo.variant != VariantCrazyhouse) return -1;
6644         whichMenu = 1; // drop menu
6645         break;
6646       default:
6647         return -1;
6648     }
6649
6650     if (((*fromX = xSqr) < 0) ||
6651         ((*fromY = ySqr) < 0)) {
6652         *fromX = *fromY = -1;
6653         return -1;
6654     }
6655     if (flipView)
6656       *fromX = BOARD_WIDTH - 1 - *fromX;
6657     else
6658       *fromY = BOARD_HEIGHT - 1 - *fromY;
6659
6660     return whichMenu;
6661 }
6662
6663 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6664 {
6665 //    char * hint = lastHint;
6666     FrontEndProgramStats stats;
6667
6668     stats.which = cps == &first ? 0 : 1;
6669     stats.depth = cpstats->depth;
6670     stats.nodes = cpstats->nodes;
6671     stats.score = cpstats->score;
6672     stats.time = cpstats->time;
6673     stats.pv = cpstats->movelist;
6674     stats.hint = lastHint;
6675     stats.an_move_index = 0;
6676     stats.an_move_count = 0;
6677
6678     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6679         stats.hint = cpstats->move_name;
6680         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6681         stats.an_move_count = cpstats->nr_moves;
6682     }
6683
6684     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
6685
6686     SetProgramStats( &stats );
6687 }
6688
6689 void
6690 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6691 {       // count all piece types
6692         int p, f, r;
6693         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6694         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6695         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6696                 p = board[r][f];
6697                 pCnt[p]++;
6698                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6699                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6700                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6701                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6702                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6703                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6704         }
6705 }
6706
6707 int
6708 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6709 {
6710         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6711         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6712
6713         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6714         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6715         if(myPawns == 2 && nMine == 3) // KPP
6716             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6717         if(myPawns == 1 && nMine == 2) // KP
6718             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6719         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6720             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6721         if(myPawns) return FALSE;
6722         if(pCnt[WhiteRook+side])
6723             return pCnt[BlackRook-side] ||
6724                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6725                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6726                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6727         if(pCnt[WhiteCannon+side]) {
6728             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6729             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6730         }
6731         if(pCnt[WhiteKnight+side])
6732             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6733         return FALSE;
6734 }
6735
6736 int
6737 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6738 {
6739         VariantClass v = gameInfo.variant;
6740
6741         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6742         if(v == VariantShatranj) return TRUE; // always winnable through baring
6743         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6744         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6745
6746         if(v == VariantXiangqi) {
6747                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6748
6749                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6750                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6751                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6752                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6753                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6754                 if(stale) // we have at least one last-rank P plus perhaps C
6755                     return majors // KPKX
6756                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6757                 else // KCA*E*
6758                     return pCnt[WhiteFerz+side] // KCAK
6759                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6760                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6761                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6762
6763         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6764                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6765
6766                 if(nMine == 1) return FALSE; // bare King
6767                 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
6768                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6769                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6770                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6771                 if(pCnt[WhiteKnight+side])
6772                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6773                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6774                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6775                 if(nBishops)
6776                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6777                 if(pCnt[WhiteAlfil+side])
6778                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6779                 if(pCnt[WhiteWazir+side])
6780                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6781         }
6782
6783         return TRUE;
6784 }
6785
6786 int
6787 Adjudicate(ChessProgramState *cps)
6788 {       // [HGM] some adjudications useful with buggy engines
6789         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6790         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6791         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6792         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6793         int k, count = 0; static int bare = 1;
6794         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6795         Boolean canAdjudicate = !appData.icsActive;
6796
6797         // most tests only when we understand the game, i.e. legality-checking on
6798             if( appData.testLegality )
6799             {   /* [HGM] Some more adjudications for obstinate engines */
6800                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6801                 static int moveCount = 6;
6802                 ChessMove result;
6803                 char *reason = NULL;
6804
6805                 /* Count what is on board. */
6806                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6807
6808                 /* Some material-based adjudications that have to be made before stalemate test */
6809                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6810                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6811                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6812                      if(canAdjudicate && appData.checkMates) {
6813                          if(engineOpponent)
6814                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6815                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6816                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6817                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6818                          return 1;
6819                      }
6820                 }
6821
6822                 /* Bare King in Shatranj (loses) or Losers (wins) */
6823                 if( nrW == 1 || nrB == 1) {
6824                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6825                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6826                      if(canAdjudicate && appData.checkMates) {
6827                          if(engineOpponent)
6828                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6829                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6830                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6831                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6832                          return 1;
6833                      }
6834                   } else
6835                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6836                   {    /* bare King */
6837                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6838                         if(canAdjudicate && appData.checkMates) {
6839                             /* but only adjudicate if adjudication enabled */
6840                             if(engineOpponent)
6841                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6842                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6843                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6844                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6845                             return 1;
6846                         }
6847                   }
6848                 } else bare = 1;
6849
6850
6851             // don't wait for engine to announce game end if we can judge ourselves
6852             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6853               case MT_CHECK:
6854                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6855                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6856                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6857                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6858                             checkCnt++;
6859                         if(checkCnt >= 2) {
6860                             reason = "Xboard adjudication: 3rd check";
6861                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6862                             break;
6863                         }
6864                     }
6865                 }
6866               case MT_NONE:
6867               default:
6868                 break;
6869               case MT_STALEMATE:
6870               case MT_STAINMATE:
6871                 reason = "Xboard adjudication: Stalemate";
6872                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6873                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6874                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6875                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6876                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6877                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6878                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6879                                                                         EP_CHECKMATE : EP_WINS);
6880                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6881                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6882                 }
6883                 break;
6884               case MT_CHECKMATE:
6885                 reason = "Xboard adjudication: Checkmate";
6886                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6887                 break;
6888             }
6889
6890                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6891                     case EP_STALEMATE:
6892                         result = GameIsDrawn; break;
6893                     case EP_CHECKMATE:
6894                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6895                     case EP_WINS:
6896                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6897                     default:
6898                         result = EndOfFile;
6899                 }
6900                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6901                     if(engineOpponent)
6902                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6903                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6904                     GameEnds( result, reason, GE_XBOARD );
6905                     return 1;
6906                 }
6907
6908                 /* Next absolutely insufficient mating material. */
6909                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6910                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6911                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6912
6913                      /* always flag draws, for judging claims */
6914                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6915
6916                      if(canAdjudicate && appData.materialDraws) {
6917                          /* but only adjudicate them if adjudication enabled */
6918                          if(engineOpponent) {
6919                            SendToProgram("force\n", engineOpponent); // suppress reply
6920                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6921                          }
6922                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6923                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6924                          return 1;
6925                      }
6926                 }
6927
6928                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6929                 if(gameInfo.variant == VariantXiangqi ?
6930                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6931                  : nrW + nrB == 4 &&
6932                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6933                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6934                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6935                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6936                    ) ) {
6937                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6938                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6939                           if(engineOpponent) {
6940                             SendToProgram("force\n", engineOpponent); // suppress reply
6941                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6942                           }
6943                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6944                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6945                           return 1;
6946                      }
6947                 } else moveCount = 6;
6948             }
6949         if (appData.debugMode) { int i;
6950             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6951                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6952                     appData.drawRepeats);
6953             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6954               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6955
6956         }
6957
6958         // Repetition draws and 50-move rule can be applied independently of legality testing
6959
6960                 /* Check for rep-draws */
6961                 count = 0;
6962                 for(k = forwardMostMove-2;
6963                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6964                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6965                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6966                     k-=2)
6967                 {   int rights=0;
6968                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6969                         /* compare castling rights */
6970                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6971                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6972                                 rights++; /* King lost rights, while rook still had them */
6973                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6974                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6975                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6976                                    rights++; /* but at least one rook lost them */
6977                         }
6978                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6979                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6980                                 rights++;
6981                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6982                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6983                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6984                                    rights++;
6985                         }
6986                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6987                             && appData.drawRepeats > 1) {
6988                              /* adjudicate after user-specified nr of repeats */
6989                              int result = GameIsDrawn;
6990                              char *details = "XBoard adjudication: repetition draw";
6991                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6992                                 // [HGM] xiangqi: check for forbidden perpetuals
6993                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6994                                 for(m=forwardMostMove; m>k; m-=2) {
6995                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6996                                         ourPerpetual = 0; // the current mover did not always check
6997                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6998                                         hisPerpetual = 0; // the opponent did not always check
6999                                 }
7000                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7001                                                                         ourPerpetual, hisPerpetual);
7002                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7003                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7004                                     details = "Xboard adjudication: perpetual checking";
7005                                 } else
7006                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7007                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7008                                 } else
7009                                 // Now check for perpetual chases
7010                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7011                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7012                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7013                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7014                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7015                                         details = "Xboard adjudication: perpetual chasing";
7016                                     } else
7017                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7018                                         break; // Abort repetition-checking loop.
7019                                 }
7020                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7021                              }
7022                              if(engineOpponent) {
7023                                SendToProgram("force\n", engineOpponent); // suppress reply
7024                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7025                              }
7026                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7027                              GameEnds( result, details, GE_XBOARD );
7028                              return 1;
7029                         }
7030                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7031                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7032                     }
7033                 }
7034
7035                 /* Now we test for 50-move draws. Determine ply count */
7036                 count = forwardMostMove;
7037                 /* look for last irreversble move */
7038                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7039                     count--;
7040                 /* if we hit starting position, add initial plies */
7041                 if( count == backwardMostMove )
7042                     count -= initialRulePlies;
7043                 count = forwardMostMove - count;
7044                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7045                         // adjust reversible move counter for checks in Xiangqi
7046                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7047                         if(i < backwardMostMove) i = backwardMostMove;
7048                         while(i <= forwardMostMove) {
7049                                 lastCheck = inCheck; // check evasion does not count
7050                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7051                                 if(inCheck || lastCheck) count--; // check does not count
7052                                 i++;
7053                         }
7054                 }
7055                 if( count >= 100)
7056                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7057                          /* this is used to judge if draw claims are legal */
7058                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7059                          if(engineOpponent) {
7060                            SendToProgram("force\n", engineOpponent); // suppress reply
7061                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7062                          }
7063                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7064                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7065                          return 1;
7066                 }
7067
7068                 /* if draw offer is pending, treat it as a draw claim
7069                  * when draw condition present, to allow engines a way to
7070                  * claim draws before making their move to avoid a race
7071                  * condition occurring after their move
7072                  */
7073                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7074                          char *p = NULL;
7075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7076                              p = "Draw claim: 50-move rule";
7077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7078                              p = "Draw claim: 3-fold repetition";
7079                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7080                              p = "Draw claim: insufficient mating material";
7081                          if( p != NULL && canAdjudicate) {
7082                              if(engineOpponent) {
7083                                SendToProgram("force\n", engineOpponent); // suppress reply
7084                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7085                              }
7086                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7087                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7088                              return 1;
7089                          }
7090                 }
7091
7092                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7093                     if(engineOpponent) {
7094                       SendToProgram("force\n", engineOpponent); // suppress reply
7095                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7096                     }
7097                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7098                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7099                     return 1;
7100                 }
7101         return 0;
7102 }
7103
7104 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7105 {   // [HGM] book: this routine intercepts moves to simulate book replies
7106     char *bookHit = NULL;
7107
7108     //first determine if the incoming move brings opponent into his book
7109     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7110         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7111     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7112     if(bookHit != NULL && !cps->bookSuspend) {
7113         // make sure opponent is not going to reply after receiving move to book position
7114         SendToProgram("force\n", cps);
7115         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7116     }
7117     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7118     // now arrange restart after book miss
7119     if(bookHit) {
7120         // after a book hit we never send 'go', and the code after the call to this routine
7121         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7122         char buf[MSG_SIZ];
7123         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7124         SendToProgram(buf, cps);
7125         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7126     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7127         SendToProgram("go\n", cps);
7128         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7129     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7130         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7131             SendToProgram("go\n", cps);
7132         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7133     }
7134     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7135 }
7136
7137 char *savedMessage;
7138 ChessProgramState *savedState;
7139 void DeferredBookMove(void)
7140 {
7141         if(savedState->lastPing != savedState->lastPong)
7142                     ScheduleDelayedEvent(DeferredBookMove, 10);
7143         else
7144         HandleMachineMove(savedMessage, savedState);
7145 }
7146
7147 void
7148 HandleMachineMove(message, cps)
7149      char *message;
7150      ChessProgramState *cps;
7151 {
7152     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7153     char realname[MSG_SIZ];
7154     int fromX, fromY, toX, toY;
7155     ChessMove moveType;
7156     char promoChar;
7157     char *p;
7158     int machineWhite;
7159     char *bookHit;
7160
7161     cps->userError = 0;
7162
7163 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7164     /*
7165      * Kludge to ignore BEL characters
7166      */
7167     while (*message == '\007') message++;
7168
7169     /*
7170      * [HGM] engine debug message: ignore lines starting with '#' character
7171      */
7172     if(cps->debug && *message == '#') return;
7173
7174     /*
7175      * Look for book output
7176      */
7177     if (cps == &first && bookRequested) {
7178         if (message[0] == '\t' || message[0] == ' ') {
7179             /* Part of the book output is here; append it */
7180             strcat(bookOutput, message);
7181             strcat(bookOutput, "  \n");
7182             return;
7183         } else if (bookOutput[0] != NULLCHAR) {
7184             /* All of book output has arrived; display it */
7185             char *p = bookOutput;
7186             while (*p != NULLCHAR) {
7187                 if (*p == '\t') *p = ' ';
7188                 p++;
7189             }
7190             DisplayInformation(bookOutput);
7191             bookRequested = FALSE;
7192             /* Fall through to parse the current output */
7193         }
7194     }
7195
7196     /*
7197      * Look for machine move.
7198      */
7199     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7200         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7201     {
7202         /* This method is only useful on engines that support ping */
7203         if (cps->lastPing != cps->lastPong) {
7204           if (gameMode == BeginningOfGame) {
7205             /* Extra move from before last new; ignore */
7206             if (appData.debugMode) {
7207                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7208             }
7209           } else {
7210             if (appData.debugMode) {
7211                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7212                         cps->which, gameMode);
7213             }
7214
7215             SendToProgram("undo\n", cps);
7216           }
7217           return;
7218         }
7219
7220         switch (gameMode) {
7221           case BeginningOfGame:
7222             /* Extra move from before last reset; ignore */
7223             if (appData.debugMode) {
7224                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7225             }
7226             return;
7227
7228           case EndOfGame:
7229           case IcsIdle:
7230           default:
7231             /* Extra move after we tried to stop.  The mode test is
7232                not a reliable way of detecting this problem, but it's
7233                the best we can do on engines that don't support ping.
7234             */
7235             if (appData.debugMode) {
7236                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7237                         cps->which, gameMode);
7238             }
7239             SendToProgram("undo\n", cps);
7240             return;
7241
7242           case MachinePlaysWhite:
7243           case IcsPlayingWhite:
7244             machineWhite = TRUE;
7245             break;
7246
7247           case MachinePlaysBlack:
7248           case IcsPlayingBlack:
7249             machineWhite = FALSE;
7250             break;
7251
7252           case TwoMachinesPlay:
7253             machineWhite = (cps->twoMachinesColor[0] == 'w');
7254             break;
7255         }
7256         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7257             if (appData.debugMode) {
7258                 fprintf(debugFP,
7259                         "Ignoring move out of turn by %s, gameMode %d"
7260                         ", forwardMost %d\n",
7261                         cps->which, gameMode, forwardMostMove);
7262             }
7263             return;
7264         }
7265
7266     if (appData.debugMode) { int f = forwardMostMove;
7267         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7268                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7269                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7270     }
7271         if(cps->alphaRank) AlphaRank(machineMove, 4);
7272         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7273                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7274             /* Machine move could not be parsed; ignore it. */
7275           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7276                     machineMove, cps->which);
7277             DisplayError(buf1, 0);
7278             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7279                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7280             if (gameMode == TwoMachinesPlay) {
7281               GameEnds(machineWhite ? BlackWins : WhiteWins,
7282                        buf1, GE_XBOARD);
7283             }
7284             return;
7285         }
7286
7287         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7288         /* So we have to redo legality test with true e.p. status here,  */
7289         /* to make sure an illegal e.p. capture does not slip through,   */
7290         /* to cause a forfeit on a justified illegal-move complaint      */
7291         /* of the opponent.                                              */
7292         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7293            ChessMove moveType;
7294            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7295                              fromY, fromX, toY, toX, promoChar);
7296             if (appData.debugMode) {
7297                 int i;
7298                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7299                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7300                 fprintf(debugFP, "castling rights\n");
7301             }
7302             if(moveType == IllegalMove) {
7303               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7304                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7305                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7306                            buf1, GE_XBOARD);
7307                 return;
7308            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7309            /* [HGM] Kludge to handle engines that send FRC-style castling
7310               when they shouldn't (like TSCP-Gothic) */
7311            switch(moveType) {
7312              case WhiteASideCastleFR:
7313              case BlackASideCastleFR:
7314                toX+=2;
7315                currentMoveString[2]++;
7316                break;
7317              case WhiteHSideCastleFR:
7318              case BlackHSideCastleFR:
7319                toX--;
7320                currentMoveString[2]--;
7321                break;
7322              default: ; // nothing to do, but suppresses warning of pedantic compilers
7323            }
7324         }
7325         hintRequested = FALSE;
7326         lastHint[0] = NULLCHAR;
7327         bookRequested = FALSE;
7328         /* Program may be pondering now */
7329         cps->maybeThinking = TRUE;
7330         if (cps->sendTime == 2) cps->sendTime = 1;
7331         if (cps->offeredDraw) cps->offeredDraw--;
7332
7333         /* currentMoveString is set as a side-effect of ParseOneMove */
7334         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7335         strcat(machineMove, "\n");
7336         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7337
7338         /* [AS] Save move info*/
7339         pvInfoList[ forwardMostMove ].score = programStats.score;
7340         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7341         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7342
7343         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7344
7345         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7346         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7347             int count = 0;
7348
7349             while( count < adjudicateLossPlies ) {
7350                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7351
7352                 if( count & 1 ) {
7353                     score = -score; /* Flip score for winning side */
7354                 }
7355
7356                 if( score > adjudicateLossThreshold ) {
7357                     break;
7358                 }
7359
7360                 count++;
7361             }
7362
7363             if( count >= adjudicateLossPlies ) {
7364                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7365
7366                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7367                     "Xboard adjudication",
7368                     GE_XBOARD );
7369
7370                 return;
7371             }
7372         }
7373
7374         if(Adjudicate(cps)) {
7375             DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
7376             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7377             return; // [HGM] adjudicate: for all automatic game ends
7378         }
7379
7380 #if ZIPPY
7381         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7382             first.initDone) {
7383           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7384                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7385                 SendToICS("draw ");
7386                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7387           }
7388           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7389           ics_user_moved = 1;
7390           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7391                 char buf[3*MSG_SIZ];
7392
7393                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7394                         programStats.score / 100.,
7395                         programStats.depth,
7396                         programStats.time / 100.,
7397                         (unsigned int)programStats.nodes,
7398                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7399                         programStats.movelist);
7400                 SendToICS(buf);
7401 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7402           }
7403         }
7404 #endif
7405
7406         /* [AS] Clear stats for next move */
7407         ClearProgramStats();
7408         thinkOutput[0] = NULLCHAR;
7409         hiddenThinkOutputState = 0;
7410
7411         bookHit = NULL;
7412         if (gameMode == TwoMachinesPlay) {
7413             /* [HGM] relaying draw offers moved to after reception of move */
7414             /* and interpreting offer as claim if it brings draw condition */
7415             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7416                 SendToProgram("draw\n", cps->other);
7417             }
7418             if (cps->other->sendTime) {
7419                 SendTimeRemaining(cps->other,
7420                                   cps->other->twoMachinesColor[0] == 'w');
7421             }
7422             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7423             if (firstMove && !bookHit) {
7424                 firstMove = FALSE;
7425                 if (cps->other->useColors) {
7426                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7427                 }
7428                 SendToProgram("go\n", cps->other);
7429             }
7430             cps->other->maybeThinking = TRUE;
7431         }
7432
7433         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7434
7435         if (!pausing && appData.ringBellAfterMoves) {
7436             RingBell();
7437         }
7438
7439         /*
7440          * Reenable menu items that were disabled while
7441          * machine was thinking
7442          */
7443         if (gameMode != TwoMachinesPlay)
7444             SetUserThinkingEnables();
7445
7446         // [HGM] book: after book hit opponent has received move and is now in force mode
7447         // force the book reply into it, and then fake that it outputted this move by jumping
7448         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7449         if(bookHit) {
7450                 static char bookMove[MSG_SIZ]; // a bit generous?
7451
7452                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7453                 strcat(bookMove, bookHit);
7454                 message = bookMove;
7455                 cps = cps->other;
7456                 programStats.nodes = programStats.depth = programStats.time =
7457                 programStats.score = programStats.got_only_move = 0;
7458                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7459
7460                 if(cps->lastPing != cps->lastPong) {
7461                     savedMessage = message; // args for deferred call
7462                     savedState = cps;
7463                     ScheduleDelayedEvent(DeferredBookMove, 10);
7464                     return;
7465                 }
7466                 goto FakeBookMove;
7467         }
7468
7469         return;
7470     }
7471
7472     /* Set special modes for chess engines.  Later something general
7473      *  could be added here; for now there is just one kludge feature,
7474      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7475      *  when "xboard" is given as an interactive command.
7476      */
7477     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7478         cps->useSigint = FALSE;
7479         cps->useSigterm = FALSE;
7480     }
7481     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7482       ParseFeatures(message+8, cps);
7483       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7484     }
7485
7486     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7487       int dummy, s=6; char buf[MSG_SIZ];
7488       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7489       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7490       ParseFEN(boards[0], &dummy, message+s);
7491       DrawPosition(TRUE, boards[0]);
7492       startedFromSetupPosition = TRUE;
7493       return;
7494     }
7495     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7496      * want this, I was asked to put it in, and obliged.
7497      */
7498     if (!strncmp(message, "setboard ", 9)) {
7499         Board initial_position;
7500
7501         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7502
7503         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7504             DisplayError(_("Bad FEN received from engine"), 0);
7505             return ;
7506         } else {
7507            Reset(TRUE, FALSE);
7508            CopyBoard(boards[0], initial_position);
7509            initialRulePlies = FENrulePlies;
7510            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7511            else gameMode = MachinePlaysBlack;
7512            DrawPosition(FALSE, boards[currentMove]);
7513         }
7514         return;
7515     }
7516
7517     /*
7518      * Look for communication commands
7519      */
7520     if (!strncmp(message, "telluser ", 9)) {
7521         if(message[9] == '\\' && message[10] == '\\')
7522             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7523         DisplayNote(message + 9);
7524         return;
7525     }
7526     if (!strncmp(message, "tellusererror ", 14)) {
7527         cps->userError = 1;
7528         if(message[14] == '\\' && message[15] == '\\')
7529             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7530         DisplayError(message + 14, 0);
7531         return;
7532     }
7533     if (!strncmp(message, "tellopponent ", 13)) {
7534       if (appData.icsActive) {
7535         if (loggedOn) {
7536           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7537           SendToICS(buf1);
7538         }
7539       } else {
7540         DisplayNote(message + 13);
7541       }
7542       return;
7543     }
7544     if (!strncmp(message, "tellothers ", 11)) {
7545       if (appData.icsActive) {
7546         if (loggedOn) {
7547           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7548           SendToICS(buf1);
7549         }
7550       }
7551       return;
7552     }
7553     if (!strncmp(message, "tellall ", 8)) {
7554       if (appData.icsActive) {
7555         if (loggedOn) {
7556           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7557           SendToICS(buf1);
7558         }
7559       } else {
7560         DisplayNote(message + 8);
7561       }
7562       return;
7563     }
7564     if (strncmp(message, "warning", 7) == 0) {
7565         /* Undocumented feature, use tellusererror in new code */
7566         DisplayError(message, 0);
7567         return;
7568     }
7569     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7570         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7571         strcat(realname, " query");
7572         AskQuestion(realname, buf2, buf1, cps->pr);
7573         return;
7574     }
7575     /* Commands from the engine directly to ICS.  We don't allow these to be
7576      *  sent until we are logged on. Crafty kibitzes have been known to
7577      *  interfere with the login process.
7578      */
7579     if (loggedOn) {
7580         if (!strncmp(message, "tellics ", 8)) {
7581             SendToICS(message + 8);
7582             SendToICS("\n");
7583             return;
7584         }
7585         if (!strncmp(message, "tellicsnoalias ", 15)) {
7586             SendToICS(ics_prefix);
7587             SendToICS(message + 15);
7588             SendToICS("\n");
7589             return;
7590         }
7591         /* The following are for backward compatibility only */
7592         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7593             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7594             SendToICS(ics_prefix);
7595             SendToICS(message);
7596             SendToICS("\n");
7597             return;
7598         }
7599     }
7600     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7601         return;
7602     }
7603     /*
7604      * If the move is illegal, cancel it and redraw the board.
7605      * Also deal with other error cases.  Matching is rather loose
7606      * here to accommodate engines written before the spec.
7607      */
7608     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7609         strncmp(message, "Error", 5) == 0) {
7610         if (StrStr(message, "name") ||
7611             StrStr(message, "rating") || StrStr(message, "?") ||
7612             StrStr(message, "result") || StrStr(message, "board") ||
7613             StrStr(message, "bk") || StrStr(message, "computer") ||
7614             StrStr(message, "variant") || StrStr(message, "hint") ||
7615             StrStr(message, "random") || StrStr(message, "depth") ||
7616             StrStr(message, "accepted")) {
7617             return;
7618         }
7619         if (StrStr(message, "protover")) {
7620           /* Program is responding to input, so it's apparently done
7621              initializing, and this error message indicates it is
7622              protocol version 1.  So we don't need to wait any longer
7623              for it to initialize and send feature commands. */
7624           FeatureDone(cps, 1);
7625           cps->protocolVersion = 1;
7626           return;
7627         }
7628         cps->maybeThinking = FALSE;
7629
7630         if (StrStr(message, "draw")) {
7631             /* Program doesn't have "draw" command */
7632             cps->sendDrawOffers = 0;
7633             return;
7634         }
7635         if (cps->sendTime != 1 &&
7636             (StrStr(message, "time") || StrStr(message, "otim"))) {
7637           /* Program apparently doesn't have "time" or "otim" command */
7638           cps->sendTime = 0;
7639           return;
7640         }
7641         if (StrStr(message, "analyze")) {
7642             cps->analysisSupport = FALSE;
7643             cps->analyzing = FALSE;
7644             Reset(FALSE, TRUE);
7645             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7646             DisplayError(buf2, 0);
7647             return;
7648         }
7649         if (StrStr(message, "(no matching move)st")) {
7650           /* Special kludge for GNU Chess 4 only */
7651           cps->stKludge = TRUE;
7652           SendTimeControl(cps, movesPerSession, timeControl,
7653                           timeIncrement, appData.searchDepth,
7654                           searchTime);
7655           return;
7656         }
7657         if (StrStr(message, "(no matching move)sd")) {
7658           /* Special kludge for GNU Chess 4 only */
7659           cps->sdKludge = TRUE;
7660           SendTimeControl(cps, movesPerSession, timeControl,
7661                           timeIncrement, appData.searchDepth,
7662                           searchTime);
7663           return;
7664         }
7665         if (!StrStr(message, "llegal")) {
7666             return;
7667         }
7668         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7669             gameMode == IcsIdle) return;
7670         if (forwardMostMove <= backwardMostMove) return;
7671         if (pausing) PauseEvent();
7672       if(appData.forceIllegal) {
7673             // [HGM] illegal: machine refused move; force position after move into it
7674           SendToProgram("force\n", cps);
7675           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7676                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7677                 // when black is to move, while there might be nothing on a2 or black
7678                 // might already have the move. So send the board as if white has the move.
7679                 // But first we must change the stm of the engine, as it refused the last move
7680                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7681                 if(WhiteOnMove(forwardMostMove)) {
7682                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7683                     SendBoard(cps, forwardMostMove); // kludgeless board
7684                 } else {
7685                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7686                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7687                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7688                 }
7689           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7690             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7691                  gameMode == TwoMachinesPlay)
7692               SendToProgram("go\n", cps);
7693             return;
7694       } else
7695         if (gameMode == PlayFromGameFile) {
7696             /* Stop reading this game file */
7697             gameMode = EditGame;
7698             ModeHighlight();
7699         }
7700         /* [HGM] illegal-move claim should forfeit game when Xboard */
7701         /* only passes fully legal moves                            */
7702         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7703             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7704                                 "False illegal-move claim", GE_XBOARD );
7705             return; // do not take back move we tested as valid
7706         }
7707         currentMove = forwardMostMove-1;
7708         DisplayMove(currentMove-1); /* before DisplayMoveError */
7709         SwitchClocks(forwardMostMove-1); // [HGM] race
7710         DisplayBothClocks();
7711         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7712                 parseList[currentMove], cps->which);
7713         DisplayMoveError(buf1);
7714         DrawPosition(FALSE, boards[currentMove]);
7715         return;
7716     }
7717     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7718         /* Program has a broken "time" command that
7719            outputs a string not ending in newline.
7720            Don't use it. */
7721         cps->sendTime = 0;
7722     }
7723
7724     /*
7725      * If chess program startup fails, exit with an error message.
7726      * Attempts to recover here are futile.
7727      */
7728     if ((StrStr(message, "unknown host") != NULL)
7729         || (StrStr(message, "No remote directory") != NULL)
7730         || (StrStr(message, "not found") != NULL)
7731         || (StrStr(message, "No such file") != NULL)
7732         || (StrStr(message, "can't alloc") != NULL)
7733         || (StrStr(message, "Permission denied") != NULL)) {
7734
7735         cps->maybeThinking = FALSE;
7736         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7737                 cps->which, cps->program, cps->host, message);
7738         RemoveInputSource(cps->isr);
7739         DisplayFatalError(buf1, 0, 1);
7740         return;
7741     }
7742
7743     /*
7744      * Look for hint output
7745      */
7746     if (sscanf(message, "Hint: %s", buf1) == 1) {
7747         if (cps == &first && hintRequested) {
7748             hintRequested = FALSE;
7749             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7750                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7751                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7752                                     PosFlags(forwardMostMove),
7753                                     fromY, fromX, toY, toX, promoChar, buf1);
7754                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7755                 DisplayInformation(buf2);
7756             } else {
7757                 /* Hint move could not be parsed!? */
7758               snprintf(buf2, sizeof(buf2),
7759                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7760                         buf1, cps->which);
7761                 DisplayError(buf2, 0);
7762             }
7763         } else {
7764           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7765         }
7766         return;
7767     }
7768
7769     /*
7770      * Ignore other messages if game is not in progress
7771      */
7772     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7773         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7774
7775     /*
7776      * look for win, lose, draw, or draw offer
7777      */
7778     if (strncmp(message, "1-0", 3) == 0) {
7779         char *p, *q, *r = "";
7780         p = strchr(message, '{');
7781         if (p) {
7782             q = strchr(p, '}');
7783             if (q) {
7784                 *q = NULLCHAR;
7785                 r = p + 1;
7786             }
7787         }
7788         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7789         return;
7790     } else if (strncmp(message, "0-1", 3) == 0) {
7791         char *p, *q, *r = "";
7792         p = strchr(message, '{');
7793         if (p) {
7794             q = strchr(p, '}');
7795             if (q) {
7796                 *q = NULLCHAR;
7797                 r = p + 1;
7798             }
7799         }
7800         /* Kludge for Arasan 4.1 bug */
7801         if (strcmp(r, "Black resigns") == 0) {
7802             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7803             return;
7804         }
7805         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7806         return;
7807     } else if (strncmp(message, "1/2", 3) == 0) {
7808         char *p, *q, *r = "";
7809         p = strchr(message, '{');
7810         if (p) {
7811             q = strchr(p, '}');
7812             if (q) {
7813                 *q = NULLCHAR;
7814                 r = p + 1;
7815             }
7816         }
7817
7818         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7819         return;
7820
7821     } else if (strncmp(message, "White resign", 12) == 0) {
7822         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7823         return;
7824     } else if (strncmp(message, "Black resign", 12) == 0) {
7825         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7826         return;
7827     } else if (strncmp(message, "White matches", 13) == 0 ||
7828                strncmp(message, "Black matches", 13) == 0   ) {
7829         /* [HGM] ignore GNUShogi noises */
7830         return;
7831     } else if (strncmp(message, "White", 5) == 0 &&
7832                message[5] != '(' &&
7833                StrStr(message, "Black") == NULL) {
7834         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7835         return;
7836     } else if (strncmp(message, "Black", 5) == 0 &&
7837                message[5] != '(') {
7838         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7839         return;
7840     } else if (strcmp(message, "resign") == 0 ||
7841                strcmp(message, "computer resigns") == 0) {
7842         switch (gameMode) {
7843           case MachinePlaysBlack:
7844           case IcsPlayingBlack:
7845             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7846             break;
7847           case MachinePlaysWhite:
7848           case IcsPlayingWhite:
7849             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7850             break;
7851           case TwoMachinesPlay:
7852             if (cps->twoMachinesColor[0] == 'w')
7853               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7854             else
7855               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7856             break;
7857           default:
7858             /* can't happen */
7859             break;
7860         }
7861         return;
7862     } else if (strncmp(message, "opponent mates", 14) == 0) {
7863         switch (gameMode) {
7864           case MachinePlaysBlack:
7865           case IcsPlayingBlack:
7866             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7867             break;
7868           case MachinePlaysWhite:
7869           case IcsPlayingWhite:
7870             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7871             break;
7872           case TwoMachinesPlay:
7873             if (cps->twoMachinesColor[0] == 'w')
7874               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7875             else
7876               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7877             break;
7878           default:
7879             /* can't happen */
7880             break;
7881         }
7882         return;
7883     } else if (strncmp(message, "computer mates", 14) == 0) {
7884         switch (gameMode) {
7885           case MachinePlaysBlack:
7886           case IcsPlayingBlack:
7887             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7888             break;
7889           case MachinePlaysWhite:
7890           case IcsPlayingWhite:
7891             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7892             break;
7893           case TwoMachinesPlay:
7894             if (cps->twoMachinesColor[0] == 'w')
7895               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7896             else
7897               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7898             break;
7899           default:
7900             /* can't happen */
7901             break;
7902         }
7903         return;
7904     } else if (strncmp(message, "checkmate", 9) == 0) {
7905         if (WhiteOnMove(forwardMostMove)) {
7906             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7907         } else {
7908             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7909         }
7910         return;
7911     } else if (strstr(message, "Draw") != NULL ||
7912                strstr(message, "game is a draw") != NULL) {
7913         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7914         return;
7915     } else if (strstr(message, "offer") != NULL &&
7916                strstr(message, "draw") != NULL) {
7917 #if ZIPPY
7918         if (appData.zippyPlay && first.initDone) {
7919             /* Relay offer to ICS */
7920             SendToICS(ics_prefix);
7921             SendToICS("draw\n");
7922         }
7923 #endif
7924         cps->offeredDraw = 2; /* valid until this engine moves twice */
7925         if (gameMode == TwoMachinesPlay) {
7926             if (cps->other->offeredDraw) {
7927                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7928             /* [HGM] in two-machine mode we delay relaying draw offer      */
7929             /* until after we also have move, to see if it is really claim */
7930             }
7931         } else if (gameMode == MachinePlaysWhite ||
7932                    gameMode == MachinePlaysBlack) {
7933           if (userOfferedDraw) {
7934             DisplayInformation(_("Machine accepts your draw offer"));
7935             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7936           } else {
7937             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7938           }
7939         }
7940     }
7941
7942
7943     /*
7944      * Look for thinking output
7945      */
7946     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7947           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7948                                 ) {
7949         int plylev, mvleft, mvtot, curscore, time;
7950         char mvname[MOVE_LEN];
7951         u64 nodes; // [DM]
7952         char plyext;
7953         int ignore = FALSE;
7954         int prefixHint = FALSE;
7955         mvname[0] = NULLCHAR;
7956
7957         switch (gameMode) {
7958           case MachinePlaysBlack:
7959           case IcsPlayingBlack:
7960             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7961             break;
7962           case MachinePlaysWhite:
7963           case IcsPlayingWhite:
7964             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7965             break;
7966           case AnalyzeMode:
7967           case AnalyzeFile:
7968             break;
7969           case IcsObserving: /* [DM] icsEngineAnalyze */
7970             if (!appData.icsEngineAnalyze) ignore = TRUE;
7971             break;
7972           case TwoMachinesPlay:
7973             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7974                 ignore = TRUE;
7975             }
7976             break;
7977           default:
7978             ignore = TRUE;
7979             break;
7980         }
7981
7982         if (!ignore) {
7983             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7984             buf1[0] = NULLCHAR;
7985             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7986                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7987
7988                 if (plyext != ' ' && plyext != '\t') {
7989                     time *= 100;
7990                 }
7991
7992                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7993                 if( cps->scoreIsAbsolute &&
7994                     ( gameMode == MachinePlaysBlack ||
7995                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7996                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7997                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7998                      !WhiteOnMove(currentMove)
7999                     ) )
8000                 {
8001                     curscore = -curscore;
8002                 }
8003
8004
8005                 tempStats.depth = plylev;
8006                 tempStats.nodes = nodes;
8007                 tempStats.time = time;
8008                 tempStats.score = curscore;
8009                 tempStats.got_only_move = 0;
8010
8011                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8012                         int ticklen;
8013
8014                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8015                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8016                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8017                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8018                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8019                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8020                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8021                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8022                 }
8023
8024                 /* Buffer overflow protection */
8025                 if (buf1[0] != NULLCHAR) {
8026                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8027                         && appData.debugMode) {
8028                         fprintf(debugFP,
8029                                 "PV is too long; using the first %u bytes.\n",
8030                                 (unsigned) sizeof(tempStats.movelist) - 1);
8031                     }
8032
8033                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8034                 } else {
8035                     sprintf(tempStats.movelist, " no PV\n");
8036                 }
8037
8038                 if (tempStats.seen_stat) {
8039                     tempStats.ok_to_send = 1;
8040                 }
8041
8042                 if (strchr(tempStats.movelist, '(') != NULL) {
8043                     tempStats.line_is_book = 1;
8044                     tempStats.nr_moves = 0;
8045                     tempStats.moves_left = 0;
8046                 } else {
8047                     tempStats.line_is_book = 0;
8048                 }
8049
8050                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8051                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8052
8053                 SendProgramStatsToFrontend( cps, &tempStats );
8054
8055                 /*
8056                     [AS] Protect the thinkOutput buffer from overflow... this
8057                     is only useful if buf1 hasn't overflowed first!
8058                 */
8059                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8060                          plylev,
8061                          (gameMode == TwoMachinesPlay ?
8062                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8063                          ((double) curscore) / 100.0,
8064                          prefixHint ? lastHint : "",
8065                          prefixHint ? " " : "" );
8066
8067                 if( buf1[0] != NULLCHAR ) {
8068                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8069
8070                     if( strlen(buf1) > max_len ) {
8071                         if( appData.debugMode) {
8072                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8073                         }
8074                         buf1[max_len+1] = '\0';
8075                     }
8076
8077                     strcat( thinkOutput, buf1 );
8078                 }
8079
8080                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8081                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8082                     DisplayMove(currentMove - 1);
8083                 }
8084                 return;
8085
8086             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8087                 /* crafty (9.25+) says "(only move) <move>"
8088                  * if there is only 1 legal move
8089                  */
8090                 sscanf(p, "(only move) %s", buf1);
8091                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8092                 sprintf(programStats.movelist, "%s (only move)", buf1);
8093                 programStats.depth = 1;
8094                 programStats.nr_moves = 1;
8095                 programStats.moves_left = 1;
8096                 programStats.nodes = 1;
8097                 programStats.time = 1;
8098                 programStats.got_only_move = 1;
8099
8100                 /* Not really, but we also use this member to
8101                    mean "line isn't going to change" (Crafty
8102                    isn't searching, so stats won't change) */
8103                 programStats.line_is_book = 1;
8104
8105                 SendProgramStatsToFrontend( cps, &programStats );
8106
8107                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8108                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8109                     DisplayMove(currentMove - 1);
8110                 }
8111                 return;
8112             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8113                               &time, &nodes, &plylev, &mvleft,
8114                               &mvtot, mvname) >= 5) {
8115                 /* The stat01: line is from Crafty (9.29+) in response
8116                    to the "." command */
8117                 programStats.seen_stat = 1;
8118                 cps->maybeThinking = TRUE;
8119
8120                 if (programStats.got_only_move || !appData.periodicUpdates)
8121                   return;
8122
8123                 programStats.depth = plylev;
8124                 programStats.time = time;
8125                 programStats.nodes = nodes;
8126                 programStats.moves_left = mvleft;
8127                 programStats.nr_moves = mvtot;
8128                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8129                 programStats.ok_to_send = 1;
8130                 programStats.movelist[0] = '\0';
8131
8132                 SendProgramStatsToFrontend( cps, &programStats );
8133
8134                 return;
8135
8136             } else if (strncmp(message,"++",2) == 0) {
8137                 /* Crafty 9.29+ outputs this */
8138                 programStats.got_fail = 2;
8139                 return;
8140
8141             } else if (strncmp(message,"--",2) == 0) {
8142                 /* Crafty 9.29+ outputs this */
8143                 programStats.got_fail = 1;
8144                 return;
8145
8146             } else if (thinkOutput[0] != NULLCHAR &&
8147                        strncmp(message, "    ", 4) == 0) {
8148                 unsigned message_len;
8149
8150                 p = message;
8151                 while (*p && *p == ' ') p++;
8152
8153                 message_len = strlen( p );
8154
8155                 /* [AS] Avoid buffer overflow */
8156                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8157                     strcat(thinkOutput, " ");
8158                     strcat(thinkOutput, p);
8159                 }
8160
8161                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8162                     strcat(programStats.movelist, " ");
8163                     strcat(programStats.movelist, p);
8164                 }
8165
8166                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8167                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8168                     DisplayMove(currentMove - 1);
8169                 }
8170                 return;
8171             }
8172         }
8173         else {
8174             buf1[0] = NULLCHAR;
8175
8176             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8177                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8178             {
8179                 ChessProgramStats cpstats;
8180
8181                 if (plyext != ' ' && plyext != '\t') {
8182                     time *= 100;
8183                 }
8184
8185                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8186                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8187                     curscore = -curscore;
8188                 }
8189
8190                 cpstats.depth = plylev;
8191                 cpstats.nodes = nodes;
8192                 cpstats.time = time;
8193                 cpstats.score = curscore;
8194                 cpstats.got_only_move = 0;
8195                 cpstats.movelist[0] = '\0';
8196
8197                 if (buf1[0] != NULLCHAR) {
8198                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8199                 }
8200
8201                 cpstats.ok_to_send = 0;
8202                 cpstats.line_is_book = 0;
8203                 cpstats.nr_moves = 0;
8204                 cpstats.moves_left = 0;
8205
8206                 SendProgramStatsToFrontend( cps, &cpstats );
8207             }
8208         }
8209     }
8210 }
8211
8212
8213 /* Parse a game score from the character string "game", and
8214    record it as the history of the current game.  The game
8215    score is NOT assumed to start from the standard position.
8216    The display is not updated in any way.
8217    */
8218 void
8219 ParseGameHistory(game)
8220      char *game;
8221 {
8222     ChessMove moveType;
8223     int fromX, fromY, toX, toY, boardIndex;
8224     char promoChar;
8225     char *p, *q;
8226     char buf[MSG_SIZ];
8227
8228     if (appData.debugMode)
8229       fprintf(debugFP, "Parsing game history: %s\n", game);
8230
8231     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8232     gameInfo.site = StrSave(appData.icsHost);
8233     gameInfo.date = PGNDate();
8234     gameInfo.round = StrSave("-");
8235
8236     /* Parse out names of players */
8237     while (*game == ' ') game++;
8238     p = buf;
8239     while (*game != ' ') *p++ = *game++;
8240     *p = NULLCHAR;
8241     gameInfo.white = StrSave(buf);
8242     while (*game == ' ') game++;
8243     p = buf;
8244     while (*game != ' ' && *game != '\n') *p++ = *game++;
8245     *p = NULLCHAR;
8246     gameInfo.black = StrSave(buf);
8247
8248     /* Parse moves */
8249     boardIndex = blackPlaysFirst ? 1 : 0;
8250     yynewstr(game);
8251     for (;;) {
8252         yyboardindex = boardIndex;
8253         moveType = (ChessMove) Myylex();
8254         switch (moveType) {
8255           case IllegalMove:             /* maybe suicide chess, etc. */
8256   if (appData.debugMode) {
8257     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8258     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8259     setbuf(debugFP, NULL);
8260   }
8261           case WhitePromotion:
8262           case BlackPromotion:
8263           case WhiteNonPromotion:
8264           case BlackNonPromotion:
8265           case NormalMove:
8266           case WhiteCapturesEnPassant:
8267           case BlackCapturesEnPassant:
8268           case WhiteKingSideCastle:
8269           case WhiteQueenSideCastle:
8270           case BlackKingSideCastle:
8271           case BlackQueenSideCastle:
8272           case WhiteKingSideCastleWild:
8273           case WhiteQueenSideCastleWild:
8274           case BlackKingSideCastleWild:
8275           case BlackQueenSideCastleWild:
8276           /* PUSH Fabien */
8277           case WhiteHSideCastleFR:
8278           case WhiteASideCastleFR:
8279           case BlackHSideCastleFR:
8280           case BlackASideCastleFR:
8281           /* POP Fabien */
8282             fromX = currentMoveString[0] - AAA;
8283             fromY = currentMoveString[1] - ONE;
8284             toX = currentMoveString[2] - AAA;
8285             toY = currentMoveString[3] - ONE;
8286             promoChar = currentMoveString[4];
8287             break;
8288           case WhiteDrop:
8289           case BlackDrop:
8290             fromX = moveType == WhiteDrop ?
8291               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8292             (int) CharToPiece(ToLower(currentMoveString[0]));
8293             fromY = DROP_RANK;
8294             toX = currentMoveString[2] - AAA;
8295             toY = currentMoveString[3] - ONE;
8296             promoChar = NULLCHAR;
8297             break;
8298           case AmbiguousMove:
8299             /* bug? */
8300             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8301   if (appData.debugMode) {
8302     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8303     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8304     setbuf(debugFP, NULL);
8305   }
8306             DisplayError(buf, 0);
8307             return;
8308           case ImpossibleMove:
8309             /* bug? */
8310             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8311   if (appData.debugMode) {
8312     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8313     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8314     setbuf(debugFP, NULL);
8315   }
8316             DisplayError(buf, 0);
8317             return;
8318           case EndOfFile:
8319             if (boardIndex < backwardMostMove) {
8320                 /* Oops, gap.  How did that happen? */
8321                 DisplayError(_("Gap in move list"), 0);
8322                 return;
8323             }
8324             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8325             if (boardIndex > forwardMostMove) {
8326                 forwardMostMove = boardIndex;
8327             }
8328             return;
8329           case ElapsedTime:
8330             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8331                 strcat(parseList[boardIndex-1], " ");
8332                 strcat(parseList[boardIndex-1], yy_text);
8333             }
8334             continue;
8335           case Comment:
8336           case PGNTag:
8337           case NAG:
8338           default:
8339             /* ignore */
8340             continue;
8341           case WhiteWins:
8342           case BlackWins:
8343           case GameIsDrawn:
8344           case GameUnfinished:
8345             if (gameMode == IcsExamining) {
8346                 if (boardIndex < backwardMostMove) {
8347                     /* Oops, gap.  How did that happen? */
8348                     return;
8349                 }
8350                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8351                 return;
8352             }
8353             gameInfo.result = moveType;
8354             p = strchr(yy_text, '{');
8355             if (p == NULL) p = strchr(yy_text, '(');
8356             if (p == NULL) {
8357                 p = yy_text;
8358                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8359             } else {
8360                 q = strchr(p, *p == '{' ? '}' : ')');
8361                 if (q != NULL) *q = NULLCHAR;
8362                 p++;
8363             }
8364             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8365             gameInfo.resultDetails = StrSave(p);
8366             continue;
8367         }
8368         if (boardIndex >= forwardMostMove &&
8369             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8370             backwardMostMove = blackPlaysFirst ? 1 : 0;
8371             return;
8372         }
8373         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8374                                  fromY, fromX, toY, toX, promoChar,
8375                                  parseList[boardIndex]);
8376         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8377         /* currentMoveString is set as a side-effect of yylex */
8378         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8379         strcat(moveList[boardIndex], "\n");
8380         boardIndex++;
8381         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8382         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8383           case MT_NONE:
8384           case MT_STALEMATE:
8385           default:
8386             break;
8387           case MT_CHECK:
8388             if(gameInfo.variant != VariantShogi)
8389                 strcat(parseList[boardIndex - 1], "+");
8390             break;
8391           case MT_CHECKMATE:
8392           case MT_STAINMATE:
8393             strcat(parseList[boardIndex - 1], "#");
8394             break;
8395         }
8396     }
8397 }
8398
8399
8400 /* Apply a move to the given board  */
8401 void
8402 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8403      int fromX, fromY, toX, toY;
8404      int promoChar;
8405      Board board;
8406 {
8407   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8408   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8409
8410     /* [HGM] compute & store e.p. status and castling rights for new position */
8411     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8412
8413       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8414       oldEP = (signed char)board[EP_STATUS];
8415       board[EP_STATUS] = EP_NONE;
8416
8417       if( board[toY][toX] != EmptySquare )
8418            board[EP_STATUS] = EP_CAPTURE;
8419
8420   if (fromY == DROP_RANK) {
8421         /* must be first */
8422         piece = board[toY][toX] = (ChessSquare) fromX;
8423   } else {
8424       int i;
8425
8426       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8427            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8428                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8429       } else
8430       if( board[fromY][fromX] == WhitePawn ) {
8431            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8432                board[EP_STATUS] = EP_PAWN_MOVE;
8433            if( toY-fromY==2) {
8434                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8435                         gameInfo.variant != VariantBerolina || toX < fromX)
8436                       board[EP_STATUS] = toX | berolina;
8437                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8438                         gameInfo.variant != VariantBerolina || toX > fromX)
8439                       board[EP_STATUS] = toX;
8440            }
8441       } else
8442       if( board[fromY][fromX] == BlackPawn ) {
8443            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8444                board[EP_STATUS] = EP_PAWN_MOVE;
8445            if( toY-fromY== -2) {
8446                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8447                         gameInfo.variant != VariantBerolina || toX < fromX)
8448                       board[EP_STATUS] = toX | berolina;
8449                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8450                         gameInfo.variant != VariantBerolina || toX > fromX)
8451                       board[EP_STATUS] = toX;
8452            }
8453        }
8454
8455        for(i=0; i<nrCastlingRights; i++) {
8456            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8457               board[CASTLING][i] == toX   && castlingRank[i] == toY
8458              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8459        }
8460
8461      if (fromX == toX && fromY == toY) return;
8462
8463      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8464      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8465      if(gameInfo.variant == VariantKnightmate)
8466          king += (int) WhiteUnicorn - (int) WhiteKing;
8467
8468     /* Code added by Tord: */
8469     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8470     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8471         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8472       board[fromY][fromX] = EmptySquare;
8473       board[toY][toX] = EmptySquare;
8474       if((toX > fromX) != (piece == WhiteRook)) {
8475         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8476       } else {
8477         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8478       }
8479     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8480                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8481       board[fromY][fromX] = EmptySquare;
8482       board[toY][toX] = EmptySquare;
8483       if((toX > fromX) != (piece == BlackRook)) {
8484         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8485       } else {
8486         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8487       }
8488     /* End of code added by Tord */
8489
8490     } else if (board[fromY][fromX] == king
8491         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8492         && toY == fromY && toX > fromX+1) {
8493         board[fromY][fromX] = EmptySquare;
8494         board[toY][toX] = king;
8495         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8496         board[fromY][BOARD_RGHT-1] = EmptySquare;
8497     } else if (board[fromY][fromX] == king
8498         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8499                && toY == fromY && toX < fromX-1) {
8500         board[fromY][fromX] = EmptySquare;
8501         board[toY][toX] = king;
8502         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8503         board[fromY][BOARD_LEFT] = EmptySquare;
8504     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8505                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8506                && toY >= BOARD_HEIGHT-promoRank
8507                ) {
8508         /* white pawn promotion */
8509         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8510         if (board[toY][toX] == EmptySquare) {
8511             board[toY][toX] = WhiteQueen;
8512         }
8513         if(gameInfo.variant==VariantBughouse ||
8514            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8515             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8516         board[fromY][fromX] = EmptySquare;
8517     } else if ((fromY == BOARD_HEIGHT-4)
8518                && (toX != fromX)
8519                && gameInfo.variant != VariantXiangqi
8520                && gameInfo.variant != VariantBerolina
8521                && (board[fromY][fromX] == WhitePawn)
8522                && (board[toY][toX] == EmptySquare)) {
8523         board[fromY][fromX] = EmptySquare;
8524         board[toY][toX] = WhitePawn;
8525         captured = board[toY - 1][toX];
8526         board[toY - 1][toX] = EmptySquare;
8527     } else if ((fromY == BOARD_HEIGHT-4)
8528                && (toX == fromX)
8529                && gameInfo.variant == VariantBerolina
8530                && (board[fromY][fromX] == WhitePawn)
8531                && (board[toY][toX] == EmptySquare)) {
8532         board[fromY][fromX] = EmptySquare;
8533         board[toY][toX] = WhitePawn;
8534         if(oldEP & EP_BEROLIN_A) {
8535                 captured = board[fromY][fromX-1];
8536                 board[fromY][fromX-1] = EmptySquare;
8537         }else{  captured = board[fromY][fromX+1];
8538                 board[fromY][fromX+1] = EmptySquare;
8539         }
8540     } else if (board[fromY][fromX] == king
8541         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8542                && toY == fromY && toX > fromX+1) {
8543         board[fromY][fromX] = EmptySquare;
8544         board[toY][toX] = king;
8545         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8546         board[fromY][BOARD_RGHT-1] = EmptySquare;
8547     } else if (board[fromY][fromX] == king
8548         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8549                && toY == fromY && toX < fromX-1) {
8550         board[fromY][fromX] = EmptySquare;
8551         board[toY][toX] = king;
8552         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8553         board[fromY][BOARD_LEFT] = EmptySquare;
8554     } else if (fromY == 7 && fromX == 3
8555                && board[fromY][fromX] == BlackKing
8556                && toY == 7 && toX == 5) {
8557         board[fromY][fromX] = EmptySquare;
8558         board[toY][toX] = BlackKing;
8559         board[fromY][7] = EmptySquare;
8560         board[toY][4] = BlackRook;
8561     } else if (fromY == 7 && fromX == 3
8562                && board[fromY][fromX] == BlackKing
8563                && toY == 7 && toX == 1) {
8564         board[fromY][fromX] = EmptySquare;
8565         board[toY][toX] = BlackKing;
8566         board[fromY][0] = EmptySquare;
8567         board[toY][2] = BlackRook;
8568     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8569                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8570                && toY < promoRank
8571                ) {
8572         /* black pawn promotion */
8573         board[toY][toX] = CharToPiece(ToLower(promoChar));
8574         if (board[toY][toX] == EmptySquare) {
8575             board[toY][toX] = BlackQueen;
8576         }
8577         if(gameInfo.variant==VariantBughouse ||
8578            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8579             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8580         board[fromY][fromX] = EmptySquare;
8581     } else if ((fromY == 3)
8582                && (toX != fromX)
8583                && gameInfo.variant != VariantXiangqi
8584                && gameInfo.variant != VariantBerolina
8585                && (board[fromY][fromX] == BlackPawn)
8586                && (board[toY][toX] == EmptySquare)) {
8587         board[fromY][fromX] = EmptySquare;
8588         board[toY][toX] = BlackPawn;
8589         captured = board[toY + 1][toX];
8590         board[toY + 1][toX] = EmptySquare;
8591     } else if ((fromY == 3)
8592                && (toX == fromX)
8593                && gameInfo.variant == VariantBerolina
8594                && (board[fromY][fromX] == BlackPawn)
8595                && (board[toY][toX] == EmptySquare)) {
8596         board[fromY][fromX] = EmptySquare;
8597         board[toY][toX] = BlackPawn;
8598         if(oldEP & EP_BEROLIN_A) {
8599                 captured = board[fromY][fromX-1];
8600                 board[fromY][fromX-1] = EmptySquare;
8601         }else{  captured = board[fromY][fromX+1];
8602                 board[fromY][fromX+1] = EmptySquare;
8603         }
8604     } else {
8605         board[toY][toX] = board[fromY][fromX];
8606         board[fromY][fromX] = EmptySquare;
8607     }
8608   }
8609
8610     if (gameInfo.holdingsWidth != 0) {
8611
8612       /* !!A lot more code needs to be written to support holdings  */
8613       /* [HGM] OK, so I have written it. Holdings are stored in the */
8614       /* penultimate board files, so they are automaticlly stored   */
8615       /* in the game history.                                       */
8616       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8617                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8618         /* Delete from holdings, by decreasing count */
8619         /* and erasing image if necessary            */
8620         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8621         if(p < (int) BlackPawn) { /* white drop */
8622              p -= (int)WhitePawn;
8623                  p = PieceToNumber((ChessSquare)p);
8624              if(p >= gameInfo.holdingsSize) p = 0;
8625              if(--board[p][BOARD_WIDTH-2] <= 0)
8626                   board[p][BOARD_WIDTH-1] = EmptySquare;
8627              if((int)board[p][BOARD_WIDTH-2] < 0)
8628                         board[p][BOARD_WIDTH-2] = 0;
8629         } else {                  /* black drop */
8630              p -= (int)BlackPawn;
8631                  p = PieceToNumber((ChessSquare)p);
8632              if(p >= gameInfo.holdingsSize) p = 0;
8633              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8634                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8635              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8636                         board[BOARD_HEIGHT-1-p][1] = 0;
8637         }
8638       }
8639       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8640           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8641         /* [HGM] holdings: Add to holdings, if holdings exist */
8642         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8643                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8644                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8645         }
8646         p = (int) captured;
8647         if (p >= (int) BlackPawn) {
8648           p -= (int)BlackPawn;
8649           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8650                   /* in Shogi restore piece to its original  first */
8651                   captured = (ChessSquare) (DEMOTED captured);
8652                   p = DEMOTED p;
8653           }
8654           p = PieceToNumber((ChessSquare)p);
8655           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8656           board[p][BOARD_WIDTH-2]++;
8657           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8658         } else {
8659           p -= (int)WhitePawn;
8660           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8661                   captured = (ChessSquare) (DEMOTED captured);
8662                   p = DEMOTED p;
8663           }
8664           p = PieceToNumber((ChessSquare)p);
8665           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8666           board[BOARD_HEIGHT-1-p][1]++;
8667           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8668         }
8669       }
8670     } else if (gameInfo.variant == VariantAtomic) {
8671       if (captured != EmptySquare) {
8672         int y, x;
8673         for (y = toY-1; y <= toY+1; y++) {
8674           for (x = toX-1; x <= toX+1; x++) {
8675             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8676                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8677               board[y][x] = EmptySquare;
8678             }
8679           }
8680         }
8681         board[toY][toX] = EmptySquare;
8682       }
8683     }
8684     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8685         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8686     } else
8687     if(promoChar == '+') {
8688         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8689         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8690     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8691         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8692     }
8693     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8694                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8695         // [HGM] superchess: take promotion piece out of holdings
8696         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8697         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8698             if(!--board[k][BOARD_WIDTH-2])
8699                 board[k][BOARD_WIDTH-1] = EmptySquare;
8700         } else {
8701             if(!--board[BOARD_HEIGHT-1-k][1])
8702                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8703         }
8704     }
8705
8706 }
8707
8708 /* Updates forwardMostMove */
8709 void
8710 MakeMove(fromX, fromY, toX, toY, promoChar)
8711      int fromX, fromY, toX, toY;
8712      int promoChar;
8713 {
8714 //    forwardMostMove++; // [HGM] bare: moved downstream
8715
8716     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8717         int timeLeft; static int lastLoadFlag=0; int king, piece;
8718         piece = boards[forwardMostMove][fromY][fromX];
8719         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8720         if(gameInfo.variant == VariantKnightmate)
8721             king += (int) WhiteUnicorn - (int) WhiteKing;
8722         if(forwardMostMove == 0) {
8723             if(blackPlaysFirst)
8724                 fprintf(serverMoves, "%s;", second.tidy);
8725             fprintf(serverMoves, "%s;", first.tidy);
8726             if(!blackPlaysFirst)
8727                 fprintf(serverMoves, "%s;", second.tidy);
8728         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8729         lastLoadFlag = loadFlag;
8730         // print base move
8731         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8732         // print castling suffix
8733         if( toY == fromY && piece == king ) {
8734             if(toX-fromX > 1)
8735                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8736             if(fromX-toX >1)
8737                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8738         }
8739         // e.p. suffix
8740         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8741              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8742              boards[forwardMostMove][toY][toX] == EmptySquare
8743              && fromX != toX && fromY != toY)
8744                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8745         // promotion suffix
8746         if(promoChar != NULLCHAR)
8747                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8748         if(!loadFlag) {
8749             fprintf(serverMoves, "/%d/%d",
8750                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8751             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8752             else                      timeLeft = blackTimeRemaining/1000;
8753             fprintf(serverMoves, "/%d", timeLeft);
8754         }
8755         fflush(serverMoves);
8756     }
8757
8758     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8759       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8760                         0, 1);
8761       return;
8762     }
8763     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8764     if (commentList[forwardMostMove+1] != NULL) {
8765         free(commentList[forwardMostMove+1]);
8766         commentList[forwardMostMove+1] = NULL;
8767     }
8768     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8769     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8770     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8771     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8772     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8773     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8774     gameInfo.result = GameUnfinished;
8775     if (gameInfo.resultDetails != NULL) {
8776         free(gameInfo.resultDetails);
8777         gameInfo.resultDetails = NULL;
8778     }
8779     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8780                               moveList[forwardMostMove - 1]);
8781     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8782                              PosFlags(forwardMostMove - 1),
8783                              fromY, fromX, toY, toX, promoChar,
8784                              parseList[forwardMostMove - 1]);
8785     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8786       case MT_NONE:
8787       case MT_STALEMATE:
8788       default:
8789         break;
8790       case MT_CHECK:
8791         if(gameInfo.variant != VariantShogi)
8792             strcat(parseList[forwardMostMove - 1], "+");
8793         break;
8794       case MT_CHECKMATE:
8795       case MT_STAINMATE:
8796         strcat(parseList[forwardMostMove - 1], "#");
8797         break;
8798     }
8799     if (appData.debugMode) {
8800         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8801     }
8802
8803 }
8804
8805 /* Updates currentMove if not pausing */
8806 void
8807 ShowMove(fromX, fromY, toX, toY)
8808 {
8809     int instant = (gameMode == PlayFromGameFile) ?
8810         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8811     if(appData.noGUI) return;
8812     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8813         if (!instant) {
8814             if (forwardMostMove == currentMove + 1) {
8815                 AnimateMove(boards[forwardMostMove - 1],
8816                             fromX, fromY, toX, toY);
8817             }
8818             if (appData.highlightLastMove) {
8819                 SetHighlights(fromX, fromY, toX, toY);
8820             }
8821         }
8822         currentMove = forwardMostMove;
8823     }
8824
8825     if (instant) return;
8826
8827     DisplayMove(currentMove - 1);
8828     DrawPosition(FALSE, boards[currentMove]);
8829     DisplayBothClocks();
8830     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8831 }
8832
8833 void SendEgtPath(ChessProgramState *cps)
8834 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8835         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8836
8837         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8838
8839         while(*p) {
8840             char c, *q = name+1, *r, *s;
8841
8842             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8843             while(*p && *p != ',') *q++ = *p++;
8844             *q++ = ':'; *q = 0;
8845             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8846                 strcmp(name, ",nalimov:") == 0 ) {
8847                 // take nalimov path from the menu-changeable option first, if it is defined
8848               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8849                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8850             } else
8851             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8852                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8853                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8854                 s = r = StrStr(s, ":") + 1; // beginning of path info
8855                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8856                 c = *r; *r = 0;             // temporarily null-terminate path info
8857                     *--q = 0;               // strip of trailig ':' from name
8858                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8859                 *r = c;
8860                 SendToProgram(buf,cps);     // send egtbpath command for this format
8861             }
8862             if(*p == ',') p++; // read away comma to position for next format name
8863         }
8864 }
8865
8866 void
8867 InitChessProgram(cps, setup)
8868      ChessProgramState *cps;
8869      int setup; /* [HGM] needed to setup FRC opening position */
8870 {
8871     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8872     if (appData.noChessProgram) return;
8873     hintRequested = FALSE;
8874     bookRequested = FALSE;
8875
8876     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8877     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8878     if(cps->memSize) { /* [HGM] memory */
8879       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8880         SendToProgram(buf, cps);
8881     }
8882     SendEgtPath(cps); /* [HGM] EGT */
8883     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8884       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8885         SendToProgram(buf, cps);
8886     }
8887
8888     SendToProgram(cps->initString, cps);
8889     if (gameInfo.variant != VariantNormal &&
8890         gameInfo.variant != VariantLoadable
8891         /* [HGM] also send variant if board size non-standard */
8892         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8893                                             ) {
8894       char *v = VariantName(gameInfo.variant);
8895       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8896         /* [HGM] in protocol 1 we have to assume all variants valid */
8897         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8898         DisplayFatalError(buf, 0, 1);
8899         return;
8900       }
8901
8902       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8903       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8904       if( gameInfo.variant == VariantXiangqi )
8905            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8906       if( gameInfo.variant == VariantShogi )
8907            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8908       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8909            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8910       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8911                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8912            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8913       if( gameInfo.variant == VariantCourier )
8914            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8915       if( gameInfo.variant == VariantSuper )
8916            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8917       if( gameInfo.variant == VariantGreat )
8918            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8919       if( gameInfo.variant == VariantSChess )
8920            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8921
8922       if(overruled) {
8923         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8924                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8925            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8926            if(StrStr(cps->variants, b) == NULL) {
8927                // specific sized variant not known, check if general sizing allowed
8928                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8929                    if(StrStr(cps->variants, "boardsize") == NULL) {
8930                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8931                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8932                        DisplayFatalError(buf, 0, 1);
8933                        return;
8934                    }
8935                    /* [HGM] here we really should compare with the maximum supported board size */
8936                }
8937            }
8938       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8939       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8940       SendToProgram(buf, cps);
8941     }
8942     currentlyInitializedVariant = gameInfo.variant;
8943
8944     /* [HGM] send opening position in FRC to first engine */
8945     if(setup) {
8946           SendToProgram("force\n", cps);
8947           SendBoard(cps, 0);
8948           /* engine is now in force mode! Set flag to wake it up after first move. */
8949           setboardSpoiledMachineBlack = 1;
8950     }
8951
8952     if (cps->sendICS) {
8953       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8954       SendToProgram(buf, cps);
8955     }
8956     cps->maybeThinking = FALSE;
8957     cps->offeredDraw = 0;
8958     if (!appData.icsActive) {
8959         SendTimeControl(cps, movesPerSession, timeControl,
8960                         timeIncrement, appData.searchDepth,
8961                         searchTime);
8962     }
8963     if (appData.showThinking
8964         // [HGM] thinking: four options require thinking output to be sent
8965         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8966                                 ) {
8967         SendToProgram("post\n", cps);
8968     }
8969     SendToProgram("hard\n", cps);
8970     if (!appData.ponderNextMove) {
8971         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8972            it without being sure what state we are in first.  "hard"
8973            is not a toggle, so that one is OK.
8974          */
8975         SendToProgram("easy\n", cps);
8976     }
8977     if (cps->usePing) {
8978       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8979       SendToProgram(buf, cps);
8980     }
8981     cps->initDone = TRUE;
8982 }
8983
8984
8985 void
8986 StartChessProgram(cps)
8987      ChessProgramState *cps;
8988 {
8989     char buf[MSG_SIZ];
8990     int err;
8991
8992     if (appData.noChessProgram) return;
8993     cps->initDone = FALSE;
8994
8995     if (strcmp(cps->host, "localhost") == 0) {
8996         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8997     } else if (*appData.remoteShell == NULLCHAR) {
8998         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8999     } else {
9000         if (*appData.remoteUser == NULLCHAR) {
9001           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9002                     cps->program);
9003         } else {
9004           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9005                     cps->host, appData.remoteUser, cps->program);
9006         }
9007         err = StartChildProcess(buf, "", &cps->pr);
9008     }
9009
9010     if (err != 0) {
9011       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9012         DisplayFatalError(buf, err, 1);
9013         cps->pr = NoProc;
9014         cps->isr = NULL;
9015         return;
9016     }
9017
9018     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9019     if (cps->protocolVersion > 1) {
9020       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9021       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9022       cps->comboCnt = 0;  //                and values of combo boxes
9023       SendToProgram(buf, cps);
9024     } else {
9025       SendToProgram("xboard\n", cps);
9026     }
9027 }
9028
9029
9030 void
9031 TwoMachinesEventIfReady P((void))
9032 {
9033   if (first.lastPing != first.lastPong) {
9034     DisplayMessage("", _("Waiting for first chess program"));
9035     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9036     return;
9037   }
9038   if (second.lastPing != second.lastPong) {
9039     DisplayMessage("", _("Waiting for second chess program"));
9040     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9041     return;
9042   }
9043   ThawUI();
9044   TwoMachinesEvent();
9045 }
9046
9047 void
9048 NextMatchGame P((void))
9049 {
9050     int index; /* [HGM] autoinc: step load index during match */
9051     Reset(FALSE, TRUE);
9052     if (*appData.loadGameFile != NULLCHAR) {
9053         index = appData.loadGameIndex;
9054         if(index < 0) { // [HGM] autoinc
9055             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9056             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9057         }
9058         LoadGameFromFile(appData.loadGameFile,
9059                          index,
9060                          appData.loadGameFile, FALSE);
9061     } else if (*appData.loadPositionFile != NULLCHAR) {
9062         index = appData.loadPositionIndex;
9063         if(index < 0) { // [HGM] autoinc
9064             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9065             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9066         }
9067         LoadPositionFromFile(appData.loadPositionFile,
9068                              index,
9069                              appData.loadPositionFile);
9070     }
9071     TwoMachinesEventIfReady();
9072 }
9073
9074 void UserAdjudicationEvent( int result )
9075 {
9076     ChessMove gameResult = GameIsDrawn;
9077
9078     if( result > 0 ) {
9079         gameResult = WhiteWins;
9080     }
9081     else if( result < 0 ) {
9082         gameResult = BlackWins;
9083     }
9084
9085     if( gameMode == TwoMachinesPlay ) {
9086         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9087     }
9088 }
9089
9090
9091 // [HGM] save: calculate checksum of game to make games easily identifiable
9092 int StringCheckSum(char *s)
9093 {
9094         int i = 0;
9095         if(s==NULL) return 0;
9096         while(*s) i = i*259 + *s++;
9097         return i;
9098 }
9099
9100 int GameCheckSum()
9101 {
9102         int i, sum=0;
9103         for(i=backwardMostMove; i<forwardMostMove; i++) {
9104                 sum += pvInfoList[i].depth;
9105                 sum += StringCheckSum(parseList[i]);
9106                 sum += StringCheckSum(commentList[i]);
9107                 sum *= 261;
9108         }
9109         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9110         return sum + StringCheckSum(commentList[i]);
9111 } // end of save patch
9112
9113 void
9114 GameEnds(result, resultDetails, whosays)
9115      ChessMove result;
9116      char *resultDetails;
9117      int whosays;
9118 {
9119     GameMode nextGameMode;
9120     int isIcsGame;
9121     char buf[MSG_SIZ], popupRequested = 0;
9122
9123     if(endingGame) return; /* [HGM] crash: forbid recursion */
9124     endingGame = 1;
9125     if(twoBoards) { // [HGM] dual: switch back to one board
9126         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9127         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9128     }
9129     if (appData.debugMode) {
9130       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9131               result, resultDetails ? resultDetails : "(null)", whosays);
9132     }
9133
9134     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9135
9136     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9137         /* If we are playing on ICS, the server decides when the
9138            game is over, but the engine can offer to draw, claim
9139            a draw, or resign.
9140          */
9141 #if ZIPPY
9142         if (appData.zippyPlay && first.initDone) {
9143             if (result == GameIsDrawn) {
9144                 /* In case draw still needs to be claimed */
9145                 SendToICS(ics_prefix);
9146                 SendToICS("draw\n");
9147             } else if (StrCaseStr(resultDetails, "resign")) {
9148                 SendToICS(ics_prefix);
9149                 SendToICS("resign\n");
9150             }
9151         }
9152 #endif
9153         endingGame = 0; /* [HGM] crash */
9154         return;
9155     }
9156
9157     /* If we're loading the game from a file, stop */
9158     if (whosays == GE_FILE) {
9159       (void) StopLoadGameTimer();
9160       gameFileFP = NULL;
9161     }
9162
9163     /* Cancel draw offers */
9164     first.offeredDraw = second.offeredDraw = 0;
9165
9166     /* If this is an ICS game, only ICS can really say it's done;
9167        if not, anyone can. */
9168     isIcsGame = (gameMode == IcsPlayingWhite ||
9169                  gameMode == IcsPlayingBlack ||
9170                  gameMode == IcsObserving    ||
9171                  gameMode == IcsExamining);
9172
9173     if (!isIcsGame || whosays == GE_ICS) {
9174         /* OK -- not an ICS game, or ICS said it was done */
9175         StopClocks();
9176         if (!isIcsGame && !appData.noChessProgram)
9177           SetUserThinkingEnables();
9178
9179         /* [HGM] if a machine claims the game end we verify this claim */
9180         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9181             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9182                 char claimer;
9183                 ChessMove trueResult = (ChessMove) -1;
9184
9185                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9186                                             first.twoMachinesColor[0] :
9187                                             second.twoMachinesColor[0] ;
9188
9189                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9190                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9191                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9192                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9193                 } else
9194                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9195                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9196                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9197                 } else
9198                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9199                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9200                 }
9201
9202                 // now verify win claims, but not in drop games, as we don't understand those yet
9203                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9204                                                  || gameInfo.variant == VariantGreat) &&
9205                     (result == WhiteWins && claimer == 'w' ||
9206                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9207                       if (appData.debugMode) {
9208                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9209                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9210                       }
9211                       if(result != trueResult) {
9212                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9213                               result = claimer == 'w' ? BlackWins : WhiteWins;
9214                               resultDetails = buf;
9215                       }
9216                 } else
9217                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9218                     && (forwardMostMove <= backwardMostMove ||
9219                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9220                         (claimer=='b')==(forwardMostMove&1))
9221                                                                                   ) {
9222                       /* [HGM] verify: draws that were not flagged are false claims */
9223                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9224                       result = claimer == 'w' ? BlackWins : WhiteWins;
9225                       resultDetails = buf;
9226                 }
9227                 /* (Claiming a loss is accepted no questions asked!) */
9228             }
9229             /* [HGM] bare: don't allow bare King to win */
9230             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9231                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9232                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9233                && result != GameIsDrawn)
9234             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9235                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9236                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9237                         if(p >= 0 && p <= (int)WhiteKing) k++;
9238                 }
9239                 if (appData.debugMode) {
9240                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9241                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9242                 }
9243                 if(k <= 1) {
9244                         result = GameIsDrawn;
9245                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9246                         resultDetails = buf;
9247                 }
9248             }
9249         }
9250
9251
9252         if(serverMoves != NULL && !loadFlag) { char c = '=';
9253             if(result==WhiteWins) c = '+';
9254             if(result==BlackWins) c = '-';
9255             if(resultDetails != NULL)
9256                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9257         }
9258         if (resultDetails != NULL) {
9259             gameInfo.result = result;
9260             gameInfo.resultDetails = StrSave(resultDetails);
9261
9262             /* display last move only if game was not loaded from file */
9263             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9264                 DisplayMove(currentMove - 1);
9265
9266             if (forwardMostMove != 0) {
9267                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9268                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9269                                                                 ) {
9270                     if (*appData.saveGameFile != NULLCHAR) {
9271                         SaveGameToFile(appData.saveGameFile, TRUE);
9272                     } else if (appData.autoSaveGames) {
9273                         AutoSaveGame();
9274                     }
9275                     if (*appData.savePositionFile != NULLCHAR) {
9276                         SavePositionToFile(appData.savePositionFile);
9277                     }
9278                 }
9279             }
9280
9281             /* Tell program how game ended in case it is learning */
9282             /* [HGM] Moved this to after saving the PGN, just in case */
9283             /* engine died and we got here through time loss. In that */
9284             /* case we will get a fatal error writing the pipe, which */
9285             /* would otherwise lose us the PGN.                       */
9286             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9287             /* output during GameEnds should never be fatal anymore   */
9288             if (gameMode == MachinePlaysWhite ||
9289                 gameMode == MachinePlaysBlack ||
9290                 gameMode == TwoMachinesPlay ||
9291                 gameMode == IcsPlayingWhite ||
9292                 gameMode == IcsPlayingBlack ||
9293                 gameMode == BeginningOfGame) {
9294                 char buf[MSG_SIZ];
9295                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9296                         resultDetails);
9297                 if (first.pr != NoProc) {
9298                     SendToProgram(buf, &first);
9299                 }
9300                 if (second.pr != NoProc &&
9301                     gameMode == TwoMachinesPlay) {
9302                     SendToProgram(buf, &second);
9303                 }
9304             }
9305         }
9306
9307         if (appData.icsActive) {
9308             if (appData.quietPlay &&
9309                 (gameMode == IcsPlayingWhite ||
9310                  gameMode == IcsPlayingBlack)) {
9311                 SendToICS(ics_prefix);
9312                 SendToICS("set shout 1\n");
9313             }
9314             nextGameMode = IcsIdle;
9315             ics_user_moved = FALSE;
9316             /* clean up premove.  It's ugly when the game has ended and the
9317              * premove highlights are still on the board.
9318              */
9319             if (gotPremove) {
9320               gotPremove = FALSE;
9321               ClearPremoveHighlights();
9322               DrawPosition(FALSE, boards[currentMove]);
9323             }
9324             if (whosays == GE_ICS) {
9325                 switch (result) {
9326                 case WhiteWins:
9327                     if (gameMode == IcsPlayingWhite)
9328                         PlayIcsWinSound();
9329                     else if(gameMode == IcsPlayingBlack)
9330                         PlayIcsLossSound();
9331                     break;
9332                 case BlackWins:
9333                     if (gameMode == IcsPlayingBlack)
9334                         PlayIcsWinSound();
9335                     else if(gameMode == IcsPlayingWhite)
9336                         PlayIcsLossSound();
9337                     break;
9338                 case GameIsDrawn:
9339                     PlayIcsDrawSound();
9340                     break;
9341                 default:
9342                     PlayIcsUnfinishedSound();
9343                 }
9344             }
9345         } else if (gameMode == EditGame ||
9346                    gameMode == PlayFromGameFile ||
9347                    gameMode == AnalyzeMode ||
9348                    gameMode == AnalyzeFile) {
9349             nextGameMode = gameMode;
9350         } else {
9351             nextGameMode = EndOfGame;
9352         }
9353         pausing = FALSE;
9354         ModeHighlight();
9355     } else {
9356         nextGameMode = gameMode;
9357     }
9358
9359     if (appData.noChessProgram) {
9360         gameMode = nextGameMode;
9361         ModeHighlight();
9362         endingGame = 0; /* [HGM] crash */
9363         return;
9364     }
9365
9366     if (first.reuse) {
9367         /* Put first chess program into idle state */
9368         if (first.pr != NoProc &&
9369             (gameMode == MachinePlaysWhite ||
9370              gameMode == MachinePlaysBlack ||
9371              gameMode == TwoMachinesPlay ||
9372              gameMode == IcsPlayingWhite ||
9373              gameMode == IcsPlayingBlack ||
9374              gameMode == BeginningOfGame)) {
9375             SendToProgram("force\n", &first);
9376             if (first.usePing) {
9377               char buf[MSG_SIZ];
9378               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9379               SendToProgram(buf, &first);
9380             }
9381         }
9382     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9383         /* Kill off first chess program */
9384         if (first.isr != NULL)
9385           RemoveInputSource(first.isr);
9386         first.isr = NULL;
9387
9388         if (first.pr != NoProc) {
9389             ExitAnalyzeMode();
9390             DoSleep( appData.delayBeforeQuit );
9391             SendToProgram("quit\n", &first);
9392             DoSleep( appData.delayAfterQuit );
9393             DestroyChildProcess(first.pr, first.useSigterm);
9394         }
9395         first.pr = NoProc;
9396     }
9397     if (second.reuse) {
9398         /* Put second chess program into idle state */
9399         if (second.pr != NoProc &&
9400             gameMode == TwoMachinesPlay) {
9401             SendToProgram("force\n", &second);
9402             if (second.usePing) {
9403               char buf[MSG_SIZ];
9404               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9405               SendToProgram(buf, &second);
9406             }
9407         }
9408     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9409         /* Kill off second chess program */
9410         if (second.isr != NULL)
9411           RemoveInputSource(second.isr);
9412         second.isr = NULL;
9413
9414         if (second.pr != NoProc) {
9415             DoSleep( appData.delayBeforeQuit );
9416             SendToProgram("quit\n", &second);
9417             DoSleep( appData.delayAfterQuit );
9418             DestroyChildProcess(second.pr, second.useSigterm);
9419         }
9420         second.pr = NoProc;
9421     }
9422
9423     if (matchMode && gameMode == TwoMachinesPlay) {
9424         switch (result) {
9425         case WhiteWins:
9426           if (first.twoMachinesColor[0] == 'w') {
9427             first.matchWins++;
9428           } else {
9429             second.matchWins++;
9430           }
9431           break;
9432         case BlackWins:
9433           if (first.twoMachinesColor[0] == 'b') {
9434             first.matchWins++;
9435           } else {
9436             second.matchWins++;
9437           }
9438           break;
9439         default:
9440           break;
9441         }
9442         if (matchGame < appData.matchGames) {
9443             char *tmp;
9444             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9445                 tmp = first.twoMachinesColor;
9446                 first.twoMachinesColor = second.twoMachinesColor;
9447                 second.twoMachinesColor = tmp;
9448             }
9449             gameMode = nextGameMode;
9450             matchGame++;
9451             if(appData.matchPause>10000 || appData.matchPause<10)
9452                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9453             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9454             endingGame = 0; /* [HGM] crash */
9455             return;
9456         } else {
9457             gameMode = nextGameMode;
9458             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9459                      first.tidy, second.tidy,
9460                      first.matchWins, second.matchWins,
9461                      appData.matchGames - (first.matchWins + second.matchWins));
9462             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9463             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9464                 first.twoMachinesColor = "black\n";
9465                 second.twoMachinesColor = "white\n";
9466             } else {
9467                 first.twoMachinesColor = "white\n";
9468                 second.twoMachinesColor = "black\n";
9469             }
9470         }
9471     }
9472     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9473         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9474       ExitAnalyzeMode();
9475     gameMode = nextGameMode;
9476     ModeHighlight();
9477     endingGame = 0;  /* [HGM] crash */
9478     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9479       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9480         matchMode = FALSE; appData.matchGames = matchGame = 0;
9481         DisplayNote(buf);
9482       }
9483     }
9484 }
9485
9486 /* Assumes program was just initialized (initString sent).
9487    Leaves program in force mode. */
9488 void
9489 FeedMovesToProgram(cps, upto)
9490      ChessProgramState *cps;
9491      int upto;
9492 {
9493     int i;
9494
9495     if (appData.debugMode)
9496       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9497               startedFromSetupPosition ? "position and " : "",
9498               backwardMostMove, upto, cps->which);
9499     if(currentlyInitializedVariant != gameInfo.variant) {
9500       char buf[MSG_SIZ];
9501         // [HGM] variantswitch: make engine aware of new variant
9502         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9503                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9504         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9505         SendToProgram(buf, cps);
9506         currentlyInitializedVariant = gameInfo.variant;
9507     }
9508     SendToProgram("force\n", cps);
9509     if (startedFromSetupPosition) {
9510         SendBoard(cps, backwardMostMove);
9511     if (appData.debugMode) {
9512         fprintf(debugFP, "feedMoves\n");
9513     }
9514     }
9515     for (i = backwardMostMove; i < upto; i++) {
9516         SendMoveToProgram(i, cps);
9517     }
9518 }
9519
9520
9521 void
9522 ResurrectChessProgram()
9523 {
9524      /* The chess program may have exited.
9525         If so, restart it and feed it all the moves made so far. */
9526
9527     if (appData.noChessProgram || first.pr != NoProc) return;
9528
9529     StartChessProgram(&first);
9530     InitChessProgram(&first, FALSE);
9531     FeedMovesToProgram(&first, currentMove);
9532
9533     if (!first.sendTime) {
9534         /* can't tell gnuchess what its clock should read,
9535            so we bow to its notion. */
9536         ResetClocks();
9537         timeRemaining[0][currentMove] = whiteTimeRemaining;
9538         timeRemaining[1][currentMove] = blackTimeRemaining;
9539     }
9540
9541     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9542                 appData.icsEngineAnalyze) && first.analysisSupport) {
9543       SendToProgram("analyze\n", &first);
9544       first.analyzing = TRUE;
9545     }
9546 }
9547
9548 /*
9549  * Button procedures
9550  */
9551 void
9552 Reset(redraw, init)
9553      int redraw, init;
9554 {
9555     int i;
9556
9557     if (appData.debugMode) {
9558         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9559                 redraw, init, gameMode);
9560     }
9561     CleanupTail(); // [HGM] vari: delete any stored variations
9562     pausing = pauseExamInvalid = FALSE;
9563     startedFromSetupPosition = blackPlaysFirst = FALSE;
9564     firstMove = TRUE;
9565     whiteFlag = blackFlag = FALSE;
9566     userOfferedDraw = FALSE;
9567     hintRequested = bookRequested = FALSE;
9568     first.maybeThinking = FALSE;
9569     second.maybeThinking = FALSE;
9570     first.bookSuspend = FALSE; // [HGM] book
9571     second.bookSuspend = FALSE;
9572     thinkOutput[0] = NULLCHAR;
9573     lastHint[0] = NULLCHAR;
9574     ClearGameInfo(&gameInfo);
9575     gameInfo.variant = StringToVariant(appData.variant);
9576     ics_user_moved = ics_clock_paused = FALSE;
9577     ics_getting_history = H_FALSE;
9578     ics_gamenum = -1;
9579     white_holding[0] = black_holding[0] = NULLCHAR;
9580     ClearProgramStats();
9581     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9582
9583     ResetFrontEnd();
9584     ClearHighlights();
9585     flipView = appData.flipView;
9586     ClearPremoveHighlights();
9587     gotPremove = FALSE;
9588     alarmSounded = FALSE;
9589
9590     GameEnds(EndOfFile, NULL, GE_PLAYER);
9591     if(appData.serverMovesName != NULL) {
9592         /* [HGM] prepare to make moves file for broadcasting */
9593         clock_t t = clock();
9594         if(serverMoves != NULL) fclose(serverMoves);
9595         serverMoves = fopen(appData.serverMovesName, "r");
9596         if(serverMoves != NULL) {
9597             fclose(serverMoves);
9598             /* delay 15 sec before overwriting, so all clients can see end */
9599             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9600         }
9601         serverMoves = fopen(appData.serverMovesName, "w");
9602     }
9603
9604     ExitAnalyzeMode();
9605     gameMode = BeginningOfGame;
9606     ModeHighlight();
9607     if(appData.icsActive) gameInfo.variant = VariantNormal;
9608     currentMove = forwardMostMove = backwardMostMove = 0;
9609     InitPosition(redraw);
9610     for (i = 0; i < MAX_MOVES; i++) {
9611         if (commentList[i] != NULL) {
9612             free(commentList[i]);
9613             commentList[i] = NULL;
9614         }
9615     }
9616     ResetClocks();
9617     timeRemaining[0][0] = whiteTimeRemaining;
9618     timeRemaining[1][0] = blackTimeRemaining;
9619     if (first.pr == NULL) {
9620         StartChessProgram(&first);
9621     }
9622     if (init) {
9623             InitChessProgram(&first, startedFromSetupPosition);
9624     }
9625     DisplayTitle("");
9626     DisplayMessage("", "");
9627     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9628     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9629 }
9630
9631 void
9632 AutoPlayGameLoop()
9633 {
9634     for (;;) {
9635         if (!AutoPlayOneMove())
9636           return;
9637         if (matchMode || appData.timeDelay == 0)
9638           continue;
9639         if (appData.timeDelay < 0)
9640           return;
9641         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9642         break;
9643     }
9644 }
9645
9646
9647 int
9648 AutoPlayOneMove()
9649 {
9650     int fromX, fromY, toX, toY;
9651
9652     if (appData.debugMode) {
9653       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9654     }
9655
9656     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9657       return FALSE;
9658
9659     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9660       pvInfoList[currentMove].depth = programStats.depth;
9661       pvInfoList[currentMove].score = programStats.score;
9662       pvInfoList[currentMove].time  = 0;
9663       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9664     }
9665
9666     if (currentMove >= forwardMostMove) {
9667       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9668       gameMode = EditGame;
9669       ModeHighlight();
9670
9671       /* [AS] Clear current move marker at the end of a game */
9672       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9673
9674       return FALSE;
9675     }
9676
9677     toX = moveList[currentMove][2] - AAA;
9678     toY = moveList[currentMove][3] - ONE;
9679
9680     if (moveList[currentMove][1] == '@') {
9681         if (appData.highlightLastMove) {
9682             SetHighlights(-1, -1, toX, toY);
9683         }
9684     } else {
9685         fromX = moveList[currentMove][0] - AAA;
9686         fromY = moveList[currentMove][1] - ONE;
9687
9688         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9689
9690         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9691
9692         if (appData.highlightLastMove) {
9693             SetHighlights(fromX, fromY, toX, toY);
9694         }
9695     }
9696     DisplayMove(currentMove);
9697     SendMoveToProgram(currentMove++, &first);
9698     DisplayBothClocks();
9699     DrawPosition(FALSE, boards[currentMove]);
9700     // [HGM] PV info: always display, routine tests if empty
9701     DisplayComment(currentMove - 1, commentList[currentMove]);
9702     return TRUE;
9703 }
9704
9705
9706 int
9707 LoadGameOneMove(readAhead)
9708      ChessMove readAhead;
9709 {
9710     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9711     char promoChar = NULLCHAR;
9712     ChessMove moveType;
9713     char move[MSG_SIZ];
9714     char *p, *q;
9715
9716     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9717         gameMode != AnalyzeMode && gameMode != Training) {
9718         gameFileFP = NULL;
9719         return FALSE;
9720     }
9721
9722     yyboardindex = forwardMostMove;
9723     if (readAhead != EndOfFile) {
9724       moveType = readAhead;
9725     } else {
9726       if (gameFileFP == NULL)
9727           return FALSE;
9728       moveType = (ChessMove) Myylex();
9729     }
9730
9731     done = FALSE;
9732     switch (moveType) {
9733       case Comment:
9734         if (appData.debugMode)
9735           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9736         p = yy_text;
9737
9738         /* append the comment but don't display it */
9739         AppendComment(currentMove, p, FALSE);
9740         return TRUE;
9741
9742       case WhiteCapturesEnPassant:
9743       case BlackCapturesEnPassant:
9744       case WhitePromotion:
9745       case BlackPromotion:
9746       case WhiteNonPromotion:
9747       case BlackNonPromotion:
9748       case NormalMove:
9749       case WhiteKingSideCastle:
9750       case WhiteQueenSideCastle:
9751       case BlackKingSideCastle:
9752       case BlackQueenSideCastle:
9753       case WhiteKingSideCastleWild:
9754       case WhiteQueenSideCastleWild:
9755       case BlackKingSideCastleWild:
9756       case BlackQueenSideCastleWild:
9757       /* PUSH Fabien */
9758       case WhiteHSideCastleFR:
9759       case WhiteASideCastleFR:
9760       case BlackHSideCastleFR:
9761       case BlackASideCastleFR:
9762       /* POP Fabien */
9763         if (appData.debugMode)
9764           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9765         fromX = currentMoveString[0] - AAA;
9766         fromY = currentMoveString[1] - ONE;
9767         toX = currentMoveString[2] - AAA;
9768         toY = currentMoveString[3] - ONE;
9769         promoChar = currentMoveString[4];
9770         break;
9771
9772       case WhiteDrop:
9773       case BlackDrop:
9774         if (appData.debugMode)
9775           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9776         fromX = moveType == WhiteDrop ?
9777           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9778         (int) CharToPiece(ToLower(currentMoveString[0]));
9779         fromY = DROP_RANK;
9780         toX = currentMoveString[2] - AAA;
9781         toY = currentMoveString[3] - ONE;
9782         break;
9783
9784       case WhiteWins:
9785       case BlackWins:
9786       case GameIsDrawn:
9787       case GameUnfinished:
9788         if (appData.debugMode)
9789           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9790         p = strchr(yy_text, '{');
9791         if (p == NULL) p = strchr(yy_text, '(');
9792         if (p == NULL) {
9793             p = yy_text;
9794             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9795         } else {
9796             q = strchr(p, *p == '{' ? '}' : ')');
9797             if (q != NULL) *q = NULLCHAR;
9798             p++;
9799         }
9800         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9801         GameEnds(moveType, p, GE_FILE);
9802         done = TRUE;
9803         if (cmailMsgLoaded) {
9804             ClearHighlights();
9805             flipView = WhiteOnMove(currentMove);
9806             if (moveType == GameUnfinished) flipView = !flipView;
9807             if (appData.debugMode)
9808               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9809         }
9810         break;
9811
9812       case EndOfFile:
9813         if (appData.debugMode)
9814           fprintf(debugFP, "Parser hit end of file\n");
9815         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9816           case MT_NONE:
9817           case MT_CHECK:
9818             break;
9819           case MT_CHECKMATE:
9820           case MT_STAINMATE:
9821             if (WhiteOnMove(currentMove)) {
9822                 GameEnds(BlackWins, "Black mates", GE_FILE);
9823             } else {
9824                 GameEnds(WhiteWins, "White mates", GE_FILE);
9825             }
9826             break;
9827           case MT_STALEMATE:
9828             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9829             break;
9830         }
9831         done = TRUE;
9832         break;
9833
9834       case MoveNumberOne:
9835         if (lastLoadGameStart == GNUChessGame) {
9836             /* GNUChessGames have numbers, but they aren't move numbers */
9837             if (appData.debugMode)
9838               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9839                       yy_text, (int) moveType);
9840             return LoadGameOneMove(EndOfFile); /* tail recursion */
9841         }
9842         /* else fall thru */
9843
9844       case XBoardGame:
9845       case GNUChessGame:
9846       case PGNTag:
9847         /* Reached start of next game in file */
9848         if (appData.debugMode)
9849           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9850         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9851           case MT_NONE:
9852           case MT_CHECK:
9853             break;
9854           case MT_CHECKMATE:
9855           case MT_STAINMATE:
9856             if (WhiteOnMove(currentMove)) {
9857                 GameEnds(BlackWins, "Black mates", GE_FILE);
9858             } else {
9859                 GameEnds(WhiteWins, "White mates", GE_FILE);
9860             }
9861             break;
9862           case MT_STALEMATE:
9863             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9864             break;
9865         }
9866         done = TRUE;
9867         break;
9868
9869       case PositionDiagram:     /* should not happen; ignore */
9870       case ElapsedTime:         /* ignore */
9871       case NAG:                 /* ignore */
9872         if (appData.debugMode)
9873           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9874                   yy_text, (int) moveType);
9875         return LoadGameOneMove(EndOfFile); /* tail recursion */
9876
9877       case IllegalMove:
9878         if (appData.testLegality) {
9879             if (appData.debugMode)
9880               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9881             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9882                     (forwardMostMove / 2) + 1,
9883                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9884             DisplayError(move, 0);
9885             done = TRUE;
9886         } else {
9887             if (appData.debugMode)
9888               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9889                       yy_text, currentMoveString);
9890             fromX = currentMoveString[0] - AAA;
9891             fromY = currentMoveString[1] - ONE;
9892             toX = currentMoveString[2] - AAA;
9893             toY = currentMoveString[3] - ONE;
9894             promoChar = currentMoveString[4];
9895         }
9896         break;
9897
9898       case AmbiguousMove:
9899         if (appData.debugMode)
9900           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9901         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9902                 (forwardMostMove / 2) + 1,
9903                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9904         DisplayError(move, 0);
9905         done = TRUE;
9906         break;
9907
9908       default:
9909       case ImpossibleMove:
9910         if (appData.debugMode)
9911           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9912         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9913                 (forwardMostMove / 2) + 1,
9914                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9915         DisplayError(move, 0);
9916         done = TRUE;
9917         break;
9918     }
9919
9920     if (done) {
9921         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9922             DrawPosition(FALSE, boards[currentMove]);
9923             DisplayBothClocks();
9924             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9925               DisplayComment(currentMove - 1, commentList[currentMove]);
9926         }
9927         (void) StopLoadGameTimer();
9928         gameFileFP = NULL;
9929         cmailOldMove = forwardMostMove;
9930         return FALSE;
9931     } else {
9932         /* currentMoveString is set as a side-effect of yylex */
9933         strcat(currentMoveString, "\n");
9934         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9935
9936         thinkOutput[0] = NULLCHAR;
9937         MakeMove(fromX, fromY, toX, toY, promoChar);
9938         currentMove = forwardMostMove;
9939         return TRUE;
9940     }
9941 }
9942
9943 /* Load the nth game from the given file */
9944 int
9945 LoadGameFromFile(filename, n, title, useList)
9946      char *filename;
9947      int n;
9948      char *title;
9949      /*Boolean*/ int useList;
9950 {
9951     FILE *f;
9952     char buf[MSG_SIZ];
9953
9954     if (strcmp(filename, "-") == 0) {
9955         f = stdin;
9956         title = "stdin";
9957     } else {
9958         f = fopen(filename, "rb");
9959         if (f == NULL) {
9960           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9961             DisplayError(buf, errno);
9962             return FALSE;
9963         }
9964     }
9965     if (fseek(f, 0, 0) == -1) {
9966         /* f is not seekable; probably a pipe */
9967         useList = FALSE;
9968     }
9969     if (useList && n == 0) {
9970         int error = GameListBuild(f);
9971         if (error) {
9972             DisplayError(_("Cannot build game list"), error);
9973         } else if (!ListEmpty(&gameList) &&
9974                    ((ListGame *) gameList.tailPred)->number > 1) {
9975             GameListPopUp(f, title);
9976             return TRUE;
9977         }
9978         GameListDestroy();
9979         n = 1;
9980     }
9981     if (n == 0) n = 1;
9982     return LoadGame(f, n, title, FALSE);
9983 }
9984
9985
9986 void
9987 MakeRegisteredMove()
9988 {
9989     int fromX, fromY, toX, toY;
9990     char promoChar;
9991     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9992         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9993           case CMAIL_MOVE:
9994           case CMAIL_DRAW:
9995             if (appData.debugMode)
9996               fprintf(debugFP, "Restoring %s for game %d\n",
9997                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9998
9999             thinkOutput[0] = NULLCHAR;
10000             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10001             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10002             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10003             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10004             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10005             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10006             MakeMove(fromX, fromY, toX, toY, promoChar);
10007             ShowMove(fromX, fromY, toX, toY);
10008
10009             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10010               case MT_NONE:
10011               case MT_CHECK:
10012                 break;
10013
10014               case MT_CHECKMATE:
10015               case MT_STAINMATE:
10016                 if (WhiteOnMove(currentMove)) {
10017                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10018                 } else {
10019                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10020                 }
10021                 break;
10022
10023               case MT_STALEMATE:
10024                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10025                 break;
10026             }
10027
10028             break;
10029
10030           case CMAIL_RESIGN:
10031             if (WhiteOnMove(currentMove)) {
10032                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10033             } else {
10034                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10035             }
10036             break;
10037
10038           case CMAIL_ACCEPT:
10039             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10040             break;
10041
10042           default:
10043             break;
10044         }
10045     }
10046
10047     return;
10048 }
10049
10050 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10051 int
10052 CmailLoadGame(f, gameNumber, title, useList)
10053      FILE *f;
10054      int gameNumber;
10055      char *title;
10056      int useList;
10057 {
10058     int retVal;
10059
10060     if (gameNumber > nCmailGames) {
10061         DisplayError(_("No more games in this message"), 0);
10062         return FALSE;
10063     }
10064     if (f == lastLoadGameFP) {
10065         int offset = gameNumber - lastLoadGameNumber;
10066         if (offset == 0) {
10067             cmailMsg[0] = NULLCHAR;
10068             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10069                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10070                 nCmailMovesRegistered--;
10071             }
10072             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10073             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10074                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10075             }
10076         } else {
10077             if (! RegisterMove()) return FALSE;
10078         }
10079     }
10080
10081     retVal = LoadGame(f, gameNumber, title, useList);
10082
10083     /* Make move registered during previous look at this game, if any */
10084     MakeRegisteredMove();
10085
10086     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10087         commentList[currentMove]
10088           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10089         DisplayComment(currentMove - 1, commentList[currentMove]);
10090     }
10091
10092     return retVal;
10093 }
10094
10095 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10096 int
10097 ReloadGame(offset)
10098      int offset;
10099 {
10100     int gameNumber = lastLoadGameNumber + offset;
10101     if (lastLoadGameFP == NULL) {
10102         DisplayError(_("No game has been loaded yet"), 0);
10103         return FALSE;
10104     }
10105     if (gameNumber <= 0) {
10106         DisplayError(_("Can't back up any further"), 0);
10107         return FALSE;
10108     }
10109     if (cmailMsgLoaded) {
10110         return CmailLoadGame(lastLoadGameFP, gameNumber,
10111                              lastLoadGameTitle, lastLoadGameUseList);
10112     } else {
10113         return LoadGame(lastLoadGameFP, gameNumber,
10114                         lastLoadGameTitle, lastLoadGameUseList);
10115     }
10116 }
10117
10118
10119
10120 /* Load the nth game from open file f */
10121 int
10122 LoadGame(f, gameNumber, title, useList)
10123      FILE *f;
10124      int gameNumber;
10125      char *title;
10126      int useList;
10127 {
10128     ChessMove cm;
10129     char buf[MSG_SIZ];
10130     int gn = gameNumber;
10131     ListGame *lg = NULL;
10132     int numPGNTags = 0;
10133     int err;
10134     GameMode oldGameMode;
10135     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10136
10137     if (appData.debugMode)
10138         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10139
10140     if (gameMode == Training )
10141         SetTrainingModeOff();
10142
10143     oldGameMode = gameMode;
10144     if (gameMode != BeginningOfGame) {
10145       Reset(FALSE, TRUE);
10146     }
10147
10148     gameFileFP = f;
10149     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10150         fclose(lastLoadGameFP);
10151     }
10152
10153     if (useList) {
10154         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10155
10156         if (lg) {
10157             fseek(f, lg->offset, 0);
10158             GameListHighlight(gameNumber);
10159             gn = 1;
10160         }
10161         else {
10162             DisplayError(_("Game number out of range"), 0);
10163             return FALSE;
10164         }
10165     } else {
10166         GameListDestroy();
10167         if (fseek(f, 0, 0) == -1) {
10168             if (f == lastLoadGameFP ?
10169                 gameNumber == lastLoadGameNumber + 1 :
10170                 gameNumber == 1) {
10171                 gn = 1;
10172             } else {
10173                 DisplayError(_("Can't seek on game file"), 0);
10174                 return FALSE;
10175             }
10176         }
10177     }
10178     lastLoadGameFP = f;
10179     lastLoadGameNumber = gameNumber;
10180     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10181     lastLoadGameUseList = useList;
10182
10183     yynewfile(f);
10184
10185     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10186       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10187                 lg->gameInfo.black);
10188             DisplayTitle(buf);
10189     } else if (*title != NULLCHAR) {
10190         if (gameNumber > 1) {
10191           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10192             DisplayTitle(buf);
10193         } else {
10194             DisplayTitle(title);
10195         }
10196     }
10197
10198     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10199         gameMode = PlayFromGameFile;
10200         ModeHighlight();
10201     }
10202
10203     currentMove = forwardMostMove = backwardMostMove = 0;
10204     CopyBoard(boards[0], initialPosition);
10205     StopClocks();
10206
10207     /*
10208      * Skip the first gn-1 games in the file.
10209      * Also skip over anything that precedes an identifiable
10210      * start of game marker, to avoid being confused by
10211      * garbage at the start of the file.  Currently
10212      * recognized start of game markers are the move number "1",
10213      * the pattern "gnuchess .* game", the pattern
10214      * "^[#;%] [^ ]* game file", and a PGN tag block.
10215      * A game that starts with one of the latter two patterns
10216      * will also have a move number 1, possibly
10217      * following a position diagram.
10218      * 5-4-02: Let's try being more lenient and allowing a game to
10219      * start with an unnumbered move.  Does that break anything?
10220      */
10221     cm = lastLoadGameStart = EndOfFile;
10222     while (gn > 0) {
10223         yyboardindex = forwardMostMove;
10224         cm = (ChessMove) Myylex();
10225         switch (cm) {
10226           case EndOfFile:
10227             if (cmailMsgLoaded) {
10228                 nCmailGames = CMAIL_MAX_GAMES - gn;
10229             } else {
10230                 Reset(TRUE, TRUE);
10231                 DisplayError(_("Game not found in file"), 0);
10232             }
10233             return FALSE;
10234
10235           case GNUChessGame:
10236           case XBoardGame:
10237             gn--;
10238             lastLoadGameStart = cm;
10239             break;
10240
10241           case MoveNumberOne:
10242             switch (lastLoadGameStart) {
10243               case GNUChessGame:
10244               case XBoardGame:
10245               case PGNTag:
10246                 break;
10247               case MoveNumberOne:
10248               case EndOfFile:
10249                 gn--;           /* count this game */
10250                 lastLoadGameStart = cm;
10251                 break;
10252               default:
10253                 /* impossible */
10254                 break;
10255             }
10256             break;
10257
10258           case PGNTag:
10259             switch (lastLoadGameStart) {
10260               case GNUChessGame:
10261               case PGNTag:
10262               case MoveNumberOne:
10263               case EndOfFile:
10264                 gn--;           /* count this game */
10265                 lastLoadGameStart = cm;
10266                 break;
10267               case XBoardGame:
10268                 lastLoadGameStart = cm; /* game counted already */
10269                 break;
10270               default:
10271                 /* impossible */
10272                 break;
10273             }
10274             if (gn > 0) {
10275                 do {
10276                     yyboardindex = forwardMostMove;
10277                     cm = (ChessMove) Myylex();
10278                 } while (cm == PGNTag || cm == Comment);
10279             }
10280             break;
10281
10282           case WhiteWins:
10283           case BlackWins:
10284           case GameIsDrawn:
10285             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10286                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10287                     != CMAIL_OLD_RESULT) {
10288                     nCmailResults ++ ;
10289                     cmailResult[  CMAIL_MAX_GAMES
10290                                 - gn - 1] = CMAIL_OLD_RESULT;
10291                 }
10292             }
10293             break;
10294
10295           case NormalMove:
10296             /* Only a NormalMove can be at the start of a game
10297              * without a position diagram. */
10298             if (lastLoadGameStart == EndOfFile ) {
10299               gn--;
10300               lastLoadGameStart = MoveNumberOne;
10301             }
10302             break;
10303
10304           default:
10305             break;
10306         }
10307     }
10308
10309     if (appData.debugMode)
10310       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10311
10312     if (cm == XBoardGame) {
10313         /* Skip any header junk before position diagram and/or move 1 */
10314         for (;;) {
10315             yyboardindex = forwardMostMove;
10316             cm = (ChessMove) Myylex();
10317
10318             if (cm == EndOfFile ||
10319                 cm == GNUChessGame || cm == XBoardGame) {
10320                 /* Empty game; pretend end-of-file and handle later */
10321                 cm = EndOfFile;
10322                 break;
10323             }
10324
10325             if (cm == MoveNumberOne || cm == PositionDiagram ||
10326                 cm == PGNTag || cm == Comment)
10327               break;
10328         }
10329     } else if (cm == GNUChessGame) {
10330         if (gameInfo.event != NULL) {
10331             free(gameInfo.event);
10332         }
10333         gameInfo.event = StrSave(yy_text);
10334     }
10335
10336     startedFromSetupPosition = FALSE;
10337     while (cm == PGNTag) {
10338         if (appData.debugMode)
10339           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10340         err = ParsePGNTag(yy_text, &gameInfo);
10341         if (!err) numPGNTags++;
10342
10343         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10344         if(gameInfo.variant != oldVariant) {
10345             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10346             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10347             InitPosition(TRUE);
10348             oldVariant = gameInfo.variant;
10349             if (appData.debugMode)
10350               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10351         }
10352
10353
10354         if (gameInfo.fen != NULL) {
10355           Board initial_position;
10356           startedFromSetupPosition = TRUE;
10357           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10358             Reset(TRUE, TRUE);
10359             DisplayError(_("Bad FEN position in file"), 0);
10360             return FALSE;
10361           }
10362           CopyBoard(boards[0], initial_position);
10363           if (blackPlaysFirst) {
10364             currentMove = forwardMostMove = backwardMostMove = 1;
10365             CopyBoard(boards[1], initial_position);
10366             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10367             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10368             timeRemaining[0][1] = whiteTimeRemaining;
10369             timeRemaining[1][1] = blackTimeRemaining;
10370             if (commentList[0] != NULL) {
10371               commentList[1] = commentList[0];
10372               commentList[0] = NULL;
10373             }
10374           } else {
10375             currentMove = forwardMostMove = backwardMostMove = 0;
10376           }
10377           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10378           {   int i;
10379               initialRulePlies = FENrulePlies;
10380               for( i=0; i< nrCastlingRights; i++ )
10381                   initialRights[i] = initial_position[CASTLING][i];
10382           }
10383           yyboardindex = forwardMostMove;
10384           free(gameInfo.fen);
10385           gameInfo.fen = NULL;
10386         }
10387
10388         yyboardindex = forwardMostMove;
10389         cm = (ChessMove) Myylex();
10390
10391         /* Handle comments interspersed among the tags */
10392         while (cm == Comment) {
10393             char *p;
10394             if (appData.debugMode)
10395               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10396             p = yy_text;
10397             AppendComment(currentMove, p, FALSE);
10398             yyboardindex = forwardMostMove;
10399             cm = (ChessMove) Myylex();
10400         }
10401     }
10402
10403     /* don't rely on existence of Event tag since if game was
10404      * pasted from clipboard the Event tag may not exist
10405      */
10406     if (numPGNTags > 0){
10407         char *tags;
10408         if (gameInfo.variant == VariantNormal) {
10409           VariantClass v = StringToVariant(gameInfo.event);
10410           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10411           if(v < VariantShogi) gameInfo.variant = v;
10412         }
10413         if (!matchMode) {
10414           if( appData.autoDisplayTags ) {
10415             tags = PGNTags(&gameInfo);
10416             TagsPopUp(tags, CmailMsg());
10417             free(tags);
10418           }
10419         }
10420     } else {
10421         /* Make something up, but don't display it now */
10422         SetGameInfo();
10423         TagsPopDown();
10424     }
10425
10426     if (cm == PositionDiagram) {
10427         int i, j;
10428         char *p;
10429         Board initial_position;
10430
10431         if (appData.debugMode)
10432           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10433
10434         if (!startedFromSetupPosition) {
10435             p = yy_text;
10436             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10437               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10438                 switch (*p) {
10439                   case '[':
10440                   case '-':
10441                   case ' ':
10442                   case '\t':
10443                   case '\n':
10444                   case '\r':
10445                     break;
10446                   default:
10447                     initial_position[i][j++] = CharToPiece(*p);
10448                     break;
10449                 }
10450             while (*p == ' ' || *p == '\t' ||
10451                    *p == '\n' || *p == '\r') p++;
10452
10453             if (strncmp(p, "black", strlen("black"))==0)
10454               blackPlaysFirst = TRUE;
10455             else
10456               blackPlaysFirst = FALSE;
10457             startedFromSetupPosition = TRUE;
10458
10459             CopyBoard(boards[0], initial_position);
10460             if (blackPlaysFirst) {
10461                 currentMove = forwardMostMove = backwardMostMove = 1;
10462                 CopyBoard(boards[1], initial_position);
10463                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10464                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10465                 timeRemaining[0][1] = whiteTimeRemaining;
10466                 timeRemaining[1][1] = blackTimeRemaining;
10467                 if (commentList[0] != NULL) {
10468                     commentList[1] = commentList[0];
10469                     commentList[0] = NULL;
10470                 }
10471             } else {
10472                 currentMove = forwardMostMove = backwardMostMove = 0;
10473             }
10474         }
10475         yyboardindex = forwardMostMove;
10476         cm = (ChessMove) Myylex();
10477     }
10478
10479     if (first.pr == NoProc) {
10480         StartChessProgram(&first);
10481     }
10482     InitChessProgram(&first, FALSE);
10483     SendToProgram("force\n", &first);
10484     if (startedFromSetupPosition) {
10485         SendBoard(&first, forwardMostMove);
10486     if (appData.debugMode) {
10487         fprintf(debugFP, "Load Game\n");
10488     }
10489         DisplayBothClocks();
10490     }
10491
10492     /* [HGM] server: flag to write setup moves in broadcast file as one */
10493     loadFlag = appData.suppressLoadMoves;
10494
10495     while (cm == Comment) {
10496         char *p;
10497         if (appData.debugMode)
10498           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10499         p = yy_text;
10500         AppendComment(currentMove, p, FALSE);
10501         yyboardindex = forwardMostMove;
10502         cm = (ChessMove) Myylex();
10503     }
10504
10505     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10506         cm == WhiteWins || cm == BlackWins ||
10507         cm == GameIsDrawn || cm == GameUnfinished) {
10508         DisplayMessage("", _("No moves in game"));
10509         if (cmailMsgLoaded) {
10510             if (appData.debugMode)
10511               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10512             ClearHighlights();
10513             flipView = FALSE;
10514         }
10515         DrawPosition(FALSE, boards[currentMove]);
10516         DisplayBothClocks();
10517         gameMode = EditGame;
10518         ModeHighlight();
10519         gameFileFP = NULL;
10520         cmailOldMove = 0;
10521         return TRUE;
10522     }
10523
10524     // [HGM] PV info: routine tests if comment empty
10525     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10526         DisplayComment(currentMove - 1, commentList[currentMove]);
10527     }
10528     if (!matchMode && appData.timeDelay != 0)
10529       DrawPosition(FALSE, boards[currentMove]);
10530
10531     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10532       programStats.ok_to_send = 1;
10533     }
10534
10535     /* if the first token after the PGN tags is a move
10536      * and not move number 1, retrieve it from the parser
10537      */
10538     if (cm != MoveNumberOne)
10539         LoadGameOneMove(cm);
10540
10541     /* load the remaining moves from the file */
10542     while (LoadGameOneMove(EndOfFile)) {
10543       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10544       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10545     }
10546
10547     /* rewind to the start of the game */
10548     currentMove = backwardMostMove;
10549
10550     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10551
10552     if (oldGameMode == AnalyzeFile ||
10553         oldGameMode == AnalyzeMode) {
10554       AnalyzeFileEvent();
10555     }
10556
10557     if (matchMode || appData.timeDelay == 0) {
10558       ToEndEvent();
10559       gameMode = EditGame;
10560       ModeHighlight();
10561     } else if (appData.timeDelay > 0) {
10562       AutoPlayGameLoop();
10563     }
10564
10565     if (appData.debugMode)
10566         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10567
10568     loadFlag = 0; /* [HGM] true game starts */
10569     return TRUE;
10570 }
10571
10572 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10573 int
10574 ReloadPosition(offset)
10575      int offset;
10576 {
10577     int positionNumber = lastLoadPositionNumber + offset;
10578     if (lastLoadPositionFP == NULL) {
10579         DisplayError(_("No position has been loaded yet"), 0);
10580         return FALSE;
10581     }
10582     if (positionNumber <= 0) {
10583         DisplayError(_("Can't back up any further"), 0);
10584         return FALSE;
10585     }
10586     return LoadPosition(lastLoadPositionFP, positionNumber,
10587                         lastLoadPositionTitle);
10588 }
10589
10590 /* Load the nth position from the given file */
10591 int
10592 LoadPositionFromFile(filename, n, title)
10593      char *filename;
10594      int n;
10595      char *title;
10596 {
10597     FILE *f;
10598     char buf[MSG_SIZ];
10599
10600     if (strcmp(filename, "-") == 0) {
10601         return LoadPosition(stdin, n, "stdin");
10602     } else {
10603         f = fopen(filename, "rb");
10604         if (f == NULL) {
10605             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10606             DisplayError(buf, errno);
10607             return FALSE;
10608         } else {
10609             return LoadPosition(f, n, title);
10610         }
10611     }
10612 }
10613
10614 /* Load the nth position from the given open file, and close it */
10615 int
10616 LoadPosition(f, positionNumber, title)
10617      FILE *f;
10618      int positionNumber;
10619      char *title;
10620 {
10621     char *p, line[MSG_SIZ];
10622     Board initial_position;
10623     int i, j, fenMode, pn;
10624
10625     if (gameMode == Training )
10626         SetTrainingModeOff();
10627
10628     if (gameMode != BeginningOfGame) {
10629         Reset(FALSE, TRUE);
10630     }
10631     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10632         fclose(lastLoadPositionFP);
10633     }
10634     if (positionNumber == 0) positionNumber = 1;
10635     lastLoadPositionFP = f;
10636     lastLoadPositionNumber = positionNumber;
10637     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10638     if (first.pr == NoProc) {
10639       StartChessProgram(&first);
10640       InitChessProgram(&first, FALSE);
10641     }
10642     pn = positionNumber;
10643     if (positionNumber < 0) {
10644         /* Negative position number means to seek to that byte offset */
10645         if (fseek(f, -positionNumber, 0) == -1) {
10646             DisplayError(_("Can't seek on position file"), 0);
10647             return FALSE;
10648         };
10649         pn = 1;
10650     } else {
10651         if (fseek(f, 0, 0) == -1) {
10652             if (f == lastLoadPositionFP ?
10653                 positionNumber == lastLoadPositionNumber + 1 :
10654                 positionNumber == 1) {
10655                 pn = 1;
10656             } else {
10657                 DisplayError(_("Can't seek on position file"), 0);
10658                 return FALSE;
10659             }
10660         }
10661     }
10662     /* See if this file is FEN or old-style xboard */
10663     if (fgets(line, MSG_SIZ, f) == NULL) {
10664         DisplayError(_("Position not found in file"), 0);
10665         return FALSE;
10666     }
10667     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10668     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10669
10670     if (pn >= 2) {
10671         if (fenMode || line[0] == '#') pn--;
10672         while (pn > 0) {
10673             /* skip positions before number pn */
10674             if (fgets(line, MSG_SIZ, f) == NULL) {
10675                 Reset(TRUE, TRUE);
10676                 DisplayError(_("Position not found in file"), 0);
10677                 return FALSE;
10678             }
10679             if (fenMode || line[0] == '#') pn--;
10680         }
10681     }
10682
10683     if (fenMode) {
10684         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10685             DisplayError(_("Bad FEN position in file"), 0);
10686             return FALSE;
10687         }
10688     } else {
10689         (void) fgets(line, MSG_SIZ, f);
10690         (void) fgets(line, MSG_SIZ, f);
10691
10692         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10693             (void) fgets(line, MSG_SIZ, f);
10694             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10695                 if (*p == ' ')
10696                   continue;
10697                 initial_position[i][j++] = CharToPiece(*p);
10698             }
10699         }
10700
10701         blackPlaysFirst = FALSE;
10702         if (!feof(f)) {
10703             (void) fgets(line, MSG_SIZ, f);
10704             if (strncmp(line, "black", strlen("black"))==0)
10705               blackPlaysFirst = TRUE;
10706         }
10707     }
10708     startedFromSetupPosition = TRUE;
10709
10710     SendToProgram("force\n", &first);
10711     CopyBoard(boards[0], initial_position);
10712     if (blackPlaysFirst) {
10713         currentMove = forwardMostMove = backwardMostMove = 1;
10714         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10715         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10716         CopyBoard(boards[1], initial_position);
10717         DisplayMessage("", _("Black to play"));
10718     } else {
10719         currentMove = forwardMostMove = backwardMostMove = 0;
10720         DisplayMessage("", _("White to play"));
10721     }
10722     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10723     SendBoard(&first, forwardMostMove);
10724     if (appData.debugMode) {
10725 int i, j;
10726   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10727   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10728         fprintf(debugFP, "Load Position\n");
10729     }
10730
10731     if (positionNumber > 1) {
10732       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10733         DisplayTitle(line);
10734     } else {
10735         DisplayTitle(title);
10736     }
10737     gameMode = EditGame;
10738     ModeHighlight();
10739     ResetClocks();
10740     timeRemaining[0][1] = whiteTimeRemaining;
10741     timeRemaining[1][1] = blackTimeRemaining;
10742     DrawPosition(FALSE, boards[currentMove]);
10743
10744     return TRUE;
10745 }
10746
10747
10748 void
10749 CopyPlayerNameIntoFileName(dest, src)
10750      char **dest, *src;
10751 {
10752     while (*src != NULLCHAR && *src != ',') {
10753         if (*src == ' ') {
10754             *(*dest)++ = '_';
10755             src++;
10756         } else {
10757             *(*dest)++ = *src++;
10758         }
10759     }
10760 }
10761
10762 char *DefaultFileName(ext)
10763      char *ext;
10764 {
10765     static char def[MSG_SIZ];
10766     char *p;
10767
10768     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10769         p = def;
10770         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10771         *p++ = '-';
10772         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10773         *p++ = '.';
10774         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10775     } else {
10776         def[0] = NULLCHAR;
10777     }
10778     return def;
10779 }
10780
10781 /* Save the current game to the given file */
10782 int
10783 SaveGameToFile(filename, append)
10784      char *filename;
10785      int append;
10786 {
10787     FILE *f;
10788     char buf[MSG_SIZ];
10789
10790     if (strcmp(filename, "-") == 0) {
10791         return SaveGame(stdout, 0, NULL);
10792     } else {
10793         f = fopen(filename, append ? "a" : "w");
10794         if (f == NULL) {
10795             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10796             DisplayError(buf, errno);
10797             return FALSE;
10798         } else {
10799             return SaveGame(f, 0, NULL);
10800         }
10801     }
10802 }
10803
10804 char *
10805 SavePart(str)
10806      char *str;
10807 {
10808     static char buf[MSG_SIZ];
10809     char *p;
10810
10811     p = strchr(str, ' ');
10812     if (p == NULL) return str;
10813     strncpy(buf, str, p - str);
10814     buf[p - str] = NULLCHAR;
10815     return buf;
10816 }
10817
10818 #define PGN_MAX_LINE 75
10819
10820 #define PGN_SIDE_WHITE  0
10821 #define PGN_SIDE_BLACK  1
10822
10823 /* [AS] */
10824 static int FindFirstMoveOutOfBook( int side )
10825 {
10826     int result = -1;
10827
10828     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10829         int index = backwardMostMove;
10830         int has_book_hit = 0;
10831
10832         if( (index % 2) != side ) {
10833             index++;
10834         }
10835
10836         while( index < forwardMostMove ) {
10837             /* Check to see if engine is in book */
10838             int depth = pvInfoList[index].depth;
10839             int score = pvInfoList[index].score;
10840             int in_book = 0;
10841
10842             if( depth <= 2 ) {
10843                 in_book = 1;
10844             }
10845             else if( score == 0 && depth == 63 ) {
10846                 in_book = 1; /* Zappa */
10847             }
10848             else if( score == 2 && depth == 99 ) {
10849                 in_book = 1; /* Abrok */
10850             }
10851
10852             has_book_hit += in_book;
10853
10854             if( ! in_book ) {
10855                 result = index;
10856
10857                 break;
10858             }
10859
10860             index += 2;
10861         }
10862     }
10863
10864     return result;
10865 }
10866
10867 /* [AS] */
10868 void GetOutOfBookInfo( char * buf )
10869 {
10870     int oob[2];
10871     int i;
10872     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10873
10874     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10875     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10876
10877     *buf = '\0';
10878
10879     if( oob[0] >= 0 || oob[1] >= 0 ) {
10880         for( i=0; i<2; i++ ) {
10881             int idx = oob[i];
10882
10883             if( idx >= 0 ) {
10884                 if( i > 0 && oob[0] >= 0 ) {
10885                     strcat( buf, "   " );
10886                 }
10887
10888                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10889                 sprintf( buf+strlen(buf), "%s%.2f",
10890                     pvInfoList[idx].score >= 0 ? "+" : "",
10891                     pvInfoList[idx].score / 100.0 );
10892             }
10893         }
10894     }
10895 }
10896
10897 /* Save game in PGN style and close the file */
10898 int
10899 SaveGamePGN(f)
10900      FILE *f;
10901 {
10902     int i, offset, linelen, newblock;
10903     time_t tm;
10904 //    char *movetext;
10905     char numtext[32];
10906     int movelen, numlen, blank;
10907     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10908
10909     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10910
10911     tm = time((time_t *) NULL);
10912
10913     PrintPGNTags(f, &gameInfo);
10914
10915     if (backwardMostMove > 0 || startedFromSetupPosition) {
10916         char *fen = PositionToFEN(backwardMostMove, NULL);
10917         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10918         fprintf(f, "\n{--------------\n");
10919         PrintPosition(f, backwardMostMove);
10920         fprintf(f, "--------------}\n");
10921         free(fen);
10922     }
10923     else {
10924         /* [AS] Out of book annotation */
10925         if( appData.saveOutOfBookInfo ) {
10926             char buf[64];
10927
10928             GetOutOfBookInfo( buf );
10929
10930             if( buf[0] != '\0' ) {
10931                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10932             }
10933         }
10934
10935         fprintf(f, "\n");
10936     }
10937
10938     i = backwardMostMove;
10939     linelen = 0;
10940     newblock = TRUE;
10941
10942     while (i < forwardMostMove) {
10943         /* Print comments preceding this move */
10944         if (commentList[i] != NULL) {
10945             if (linelen > 0) fprintf(f, "\n");
10946             fprintf(f, "%s", commentList[i]);
10947             linelen = 0;
10948             newblock = TRUE;
10949         }
10950
10951         /* Format move number */
10952         if ((i % 2) == 0)
10953           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10954         else
10955           if (newblock)
10956             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10957           else
10958             numtext[0] = NULLCHAR;
10959
10960         numlen = strlen(numtext);
10961         newblock = FALSE;
10962
10963         /* Print move number */
10964         blank = linelen > 0 && numlen > 0;
10965         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10966             fprintf(f, "\n");
10967             linelen = 0;
10968             blank = 0;
10969         }
10970         if (blank) {
10971             fprintf(f, " ");
10972             linelen++;
10973         }
10974         fprintf(f, "%s", numtext);
10975         linelen += numlen;
10976
10977         /* Get move */
10978         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10979         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10980
10981         /* Print move */
10982         blank = linelen > 0 && movelen > 0;
10983         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10984             fprintf(f, "\n");
10985             linelen = 0;
10986             blank = 0;
10987         }
10988         if (blank) {
10989             fprintf(f, " ");
10990             linelen++;
10991         }
10992         fprintf(f, "%s", move_buffer);
10993         linelen += movelen;
10994
10995         /* [AS] Add PV info if present */
10996         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10997             /* [HGM] add time */
10998             char buf[MSG_SIZ]; int seconds;
10999
11000             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11001
11002             if( seconds <= 0)
11003               buf[0] = 0;
11004             else
11005               if( seconds < 30 )
11006                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11007               else
11008                 {
11009                   seconds = (seconds + 4)/10; // round to full seconds
11010                   if( seconds < 60 )
11011                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11012                   else
11013                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11014                 }
11015
11016             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11017                       pvInfoList[i].score >= 0 ? "+" : "",
11018                       pvInfoList[i].score / 100.0,
11019                       pvInfoList[i].depth,
11020                       buf );
11021
11022             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11023
11024             /* Print score/depth */
11025             blank = linelen > 0 && movelen > 0;
11026             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11027                 fprintf(f, "\n");
11028                 linelen = 0;
11029                 blank = 0;
11030             }
11031             if (blank) {
11032                 fprintf(f, " ");
11033                 linelen++;
11034             }
11035             fprintf(f, "%s", move_buffer);
11036             linelen += movelen;
11037         }
11038
11039         i++;
11040     }
11041
11042     /* Start a new line */
11043     if (linelen > 0) fprintf(f, "\n");
11044
11045     /* Print comments after last move */
11046     if (commentList[i] != NULL) {
11047         fprintf(f, "%s\n", commentList[i]);
11048     }
11049
11050     /* Print result */
11051     if (gameInfo.resultDetails != NULL &&
11052         gameInfo.resultDetails[0] != NULLCHAR) {
11053         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11054                 PGNResult(gameInfo.result));
11055     } else {
11056         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11057     }
11058
11059     fclose(f);
11060     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11061     return TRUE;
11062 }
11063
11064 /* Save game in old style and close the file */
11065 int
11066 SaveGameOldStyle(f)
11067      FILE *f;
11068 {
11069     int i, offset;
11070     time_t tm;
11071
11072     tm = time((time_t *) NULL);
11073
11074     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11075     PrintOpponents(f);
11076
11077     if (backwardMostMove > 0 || startedFromSetupPosition) {
11078         fprintf(f, "\n[--------------\n");
11079         PrintPosition(f, backwardMostMove);
11080         fprintf(f, "--------------]\n");
11081     } else {
11082         fprintf(f, "\n");
11083     }
11084
11085     i = backwardMostMove;
11086     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11087
11088     while (i < forwardMostMove) {
11089         if (commentList[i] != NULL) {
11090             fprintf(f, "[%s]\n", commentList[i]);
11091         }
11092
11093         if ((i % 2) == 1) {
11094             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11095             i++;
11096         } else {
11097             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11098             i++;
11099             if (commentList[i] != NULL) {
11100                 fprintf(f, "\n");
11101                 continue;
11102             }
11103             if (i >= forwardMostMove) {
11104                 fprintf(f, "\n");
11105                 break;
11106             }
11107             fprintf(f, "%s\n", parseList[i]);
11108             i++;
11109         }
11110     }
11111
11112     if (commentList[i] != NULL) {
11113         fprintf(f, "[%s]\n", commentList[i]);
11114     }
11115
11116     /* This isn't really the old style, but it's close enough */
11117     if (gameInfo.resultDetails != NULL &&
11118         gameInfo.resultDetails[0] != NULLCHAR) {
11119         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11120                 gameInfo.resultDetails);
11121     } else {
11122         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11123     }
11124
11125     fclose(f);
11126     return TRUE;
11127 }
11128
11129 /* Save the current game to open file f and close the file */
11130 int
11131 SaveGame(f, dummy, dummy2)
11132      FILE *f;
11133      int dummy;
11134      char *dummy2;
11135 {
11136     if (gameMode == EditPosition) EditPositionDone(TRUE);
11137     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11138     if (appData.oldSaveStyle)
11139       return SaveGameOldStyle(f);
11140     else
11141       return SaveGamePGN(f);
11142 }
11143
11144 /* Save the current position to the given file */
11145 int
11146 SavePositionToFile(filename)
11147      char *filename;
11148 {
11149     FILE *f;
11150     char buf[MSG_SIZ];
11151
11152     if (strcmp(filename, "-") == 0) {
11153         return SavePosition(stdout, 0, NULL);
11154     } else {
11155         f = fopen(filename, "a");
11156         if (f == NULL) {
11157             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11158             DisplayError(buf, errno);
11159             return FALSE;
11160         } else {
11161             SavePosition(f, 0, NULL);
11162             return TRUE;
11163         }
11164     }
11165 }
11166
11167 /* Save the current position to the given open file and close the file */
11168 int
11169 SavePosition(f, dummy, dummy2)
11170      FILE *f;
11171      int dummy;
11172      char *dummy2;
11173 {
11174     time_t tm;
11175     char *fen;
11176
11177     if (gameMode == EditPosition) EditPositionDone(TRUE);
11178     if (appData.oldSaveStyle) {
11179         tm = time((time_t *) NULL);
11180
11181         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11182         PrintOpponents(f);
11183         fprintf(f, "[--------------\n");
11184         PrintPosition(f, currentMove);
11185         fprintf(f, "--------------]\n");
11186     } else {
11187         fen = PositionToFEN(currentMove, NULL);
11188         fprintf(f, "%s\n", fen);
11189         free(fen);
11190     }
11191     fclose(f);
11192     return TRUE;
11193 }
11194
11195 void
11196 ReloadCmailMsgEvent(unregister)
11197      int unregister;
11198 {
11199 #if !WIN32
11200     static char *inFilename = NULL;
11201     static char *outFilename;
11202     int i;
11203     struct stat inbuf, outbuf;
11204     int status;
11205
11206     /* Any registered moves are unregistered if unregister is set, */
11207     /* i.e. invoked by the signal handler */
11208     if (unregister) {
11209         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11210             cmailMoveRegistered[i] = FALSE;
11211             if (cmailCommentList[i] != NULL) {
11212                 free(cmailCommentList[i]);
11213                 cmailCommentList[i] = NULL;
11214             }
11215         }
11216         nCmailMovesRegistered = 0;
11217     }
11218
11219     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11220         cmailResult[i] = CMAIL_NOT_RESULT;
11221     }
11222     nCmailResults = 0;
11223
11224     if (inFilename == NULL) {
11225         /* Because the filenames are static they only get malloced once  */
11226         /* and they never get freed                                      */
11227         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11228         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11229
11230         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11231         sprintf(outFilename, "%s.out", appData.cmailGameName);
11232     }
11233
11234     status = stat(outFilename, &outbuf);
11235     if (status < 0) {
11236         cmailMailedMove = FALSE;
11237     } else {
11238         status = stat(inFilename, &inbuf);
11239         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11240     }
11241
11242     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11243        counts the games, notes how each one terminated, etc.
11244
11245        It would be nice to remove this kludge and instead gather all
11246        the information while building the game list.  (And to keep it
11247        in the game list nodes instead of having a bunch of fixed-size
11248        parallel arrays.)  Note this will require getting each game's
11249        termination from the PGN tags, as the game list builder does
11250        not process the game moves.  --mann
11251        */
11252     cmailMsgLoaded = TRUE;
11253     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11254
11255     /* Load first game in the file or popup game menu */
11256     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11257
11258 #endif /* !WIN32 */
11259     return;
11260 }
11261
11262 int
11263 RegisterMove()
11264 {
11265     FILE *f;
11266     char string[MSG_SIZ];
11267
11268     if (   cmailMailedMove
11269         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11270         return TRUE;            /* Allow free viewing  */
11271     }
11272
11273     /* Unregister move to ensure that we don't leave RegisterMove        */
11274     /* with the move registered when the conditions for registering no   */
11275     /* longer hold                                                       */
11276     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11277         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11278         nCmailMovesRegistered --;
11279
11280         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11281           {
11282               free(cmailCommentList[lastLoadGameNumber - 1]);
11283               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11284           }
11285     }
11286
11287     if (cmailOldMove == -1) {
11288         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11289         return FALSE;
11290     }
11291
11292     if (currentMove > cmailOldMove + 1) {
11293         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11294         return FALSE;
11295     }
11296
11297     if (currentMove < cmailOldMove) {
11298         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11299         return FALSE;
11300     }
11301
11302     if (forwardMostMove > currentMove) {
11303         /* Silently truncate extra moves */
11304         TruncateGame();
11305     }
11306
11307     if (   (currentMove == cmailOldMove + 1)
11308         || (   (currentMove == cmailOldMove)
11309             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11310                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11311         if (gameInfo.result != GameUnfinished) {
11312             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11313         }
11314
11315         if (commentList[currentMove] != NULL) {
11316             cmailCommentList[lastLoadGameNumber - 1]
11317               = StrSave(commentList[currentMove]);
11318         }
11319         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11320
11321         if (appData.debugMode)
11322           fprintf(debugFP, "Saving %s for game %d\n",
11323                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11324
11325         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11326
11327         f = fopen(string, "w");
11328         if (appData.oldSaveStyle) {
11329             SaveGameOldStyle(f); /* also closes the file */
11330
11331             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11332             f = fopen(string, "w");
11333             SavePosition(f, 0, NULL); /* also closes the file */
11334         } else {
11335             fprintf(f, "{--------------\n");
11336             PrintPosition(f, currentMove);
11337             fprintf(f, "--------------}\n\n");
11338
11339             SaveGame(f, 0, NULL); /* also closes the file*/
11340         }
11341
11342         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11343         nCmailMovesRegistered ++;
11344     } else if (nCmailGames == 1) {
11345         DisplayError(_("You have not made a move yet"), 0);
11346         return FALSE;
11347     }
11348
11349     return TRUE;
11350 }
11351
11352 void
11353 MailMoveEvent()
11354 {
11355 #if !WIN32
11356     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11357     FILE *commandOutput;
11358     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11359     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11360     int nBuffers;
11361     int i;
11362     int archived;
11363     char *arcDir;
11364
11365     if (! cmailMsgLoaded) {
11366         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11367         return;
11368     }
11369
11370     if (nCmailGames == nCmailResults) {
11371         DisplayError(_("No unfinished games"), 0);
11372         return;
11373     }
11374
11375 #if CMAIL_PROHIBIT_REMAIL
11376     if (cmailMailedMove) {
11377       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);
11378         DisplayError(msg, 0);
11379         return;
11380     }
11381 #endif
11382
11383     if (! (cmailMailedMove || RegisterMove())) return;
11384
11385     if (   cmailMailedMove
11386         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11387       snprintf(string, MSG_SIZ, partCommandString,
11388                appData.debugMode ? " -v" : "", appData.cmailGameName);
11389         commandOutput = popen(string, "r");
11390
11391         if (commandOutput == NULL) {
11392             DisplayError(_("Failed to invoke cmail"), 0);
11393         } else {
11394             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11395                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11396             }
11397             if (nBuffers > 1) {
11398                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11399                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11400                 nBytes = MSG_SIZ - 1;
11401             } else {
11402                 (void) memcpy(msg, buffer, nBytes);
11403             }
11404             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11405
11406             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11407                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11408
11409                 archived = TRUE;
11410                 for (i = 0; i < nCmailGames; i ++) {
11411                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11412                         archived = FALSE;
11413                     }
11414                 }
11415                 if (   archived
11416                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11417                         != NULL)) {
11418                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11419                            arcDir,
11420                            appData.cmailGameName,
11421                            gameInfo.date);
11422                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11423                     cmailMsgLoaded = FALSE;
11424                 }
11425             }
11426
11427             DisplayInformation(msg);
11428             pclose(commandOutput);
11429         }
11430     } else {
11431         if ((*cmailMsg) != '\0') {
11432             DisplayInformation(cmailMsg);
11433         }
11434     }
11435
11436     return;
11437 #endif /* !WIN32 */
11438 }
11439
11440 char *
11441 CmailMsg()
11442 {
11443 #if WIN32
11444     return NULL;
11445 #else
11446     int  prependComma = 0;
11447     char number[5];
11448     char string[MSG_SIZ];       /* Space for game-list */
11449     int  i;
11450
11451     if (!cmailMsgLoaded) return "";
11452
11453     if (cmailMailedMove) {
11454       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11455     } else {
11456         /* Create a list of games left */
11457       snprintf(string, MSG_SIZ, "[");
11458         for (i = 0; i < nCmailGames; i ++) {
11459             if (! (   cmailMoveRegistered[i]
11460                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11461                 if (prependComma) {
11462                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11463                 } else {
11464                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11465                     prependComma = 1;
11466                 }
11467
11468                 strcat(string, number);
11469             }
11470         }
11471         strcat(string, "]");
11472
11473         if (nCmailMovesRegistered + nCmailResults == 0) {
11474             switch (nCmailGames) {
11475               case 1:
11476                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11477                 break;
11478
11479               case 2:
11480                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11481                 break;
11482
11483               default:
11484                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11485                          nCmailGames);
11486                 break;
11487             }
11488         } else {
11489             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11490               case 1:
11491                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11492                          string);
11493                 break;
11494
11495               case 0:
11496                 if (nCmailResults == nCmailGames) {
11497                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11498                 } else {
11499                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11500                 }
11501                 break;
11502
11503               default:
11504                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11505                          string);
11506             }
11507         }
11508     }
11509     return cmailMsg;
11510 #endif /* WIN32 */
11511 }
11512
11513 void
11514 ResetGameEvent()
11515 {
11516     if (gameMode == Training)
11517       SetTrainingModeOff();
11518
11519     Reset(TRUE, TRUE);
11520     cmailMsgLoaded = FALSE;
11521     if (appData.icsActive) {
11522       SendToICS(ics_prefix);
11523       SendToICS("refresh\n");
11524     }
11525 }
11526
11527 void
11528 ExitEvent(status)
11529      int status;
11530 {
11531     exiting++;
11532     if (exiting > 2) {
11533       /* Give up on clean exit */
11534       exit(status);
11535     }
11536     if (exiting > 1) {
11537       /* Keep trying for clean exit */
11538       return;
11539     }
11540
11541     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11542
11543     if (telnetISR != NULL) {
11544       RemoveInputSource(telnetISR);
11545     }
11546     if (icsPR != NoProc) {
11547       DestroyChildProcess(icsPR, TRUE);
11548     }
11549
11550     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11551     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11552
11553     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11554     /* make sure this other one finishes before killing it!                  */
11555     if(endingGame) { int count = 0;
11556         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11557         while(endingGame && count++ < 10) DoSleep(1);
11558         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11559     }
11560
11561     /* Kill off chess programs */
11562     if (first.pr != NoProc) {
11563         ExitAnalyzeMode();
11564
11565         DoSleep( appData.delayBeforeQuit );
11566         SendToProgram("quit\n", &first);
11567         DoSleep( appData.delayAfterQuit );
11568         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11569     }
11570     if (second.pr != NoProc) {
11571         DoSleep( appData.delayBeforeQuit );
11572         SendToProgram("quit\n", &second);
11573         DoSleep( appData.delayAfterQuit );
11574         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11575     }
11576     if (first.isr != NULL) {
11577         RemoveInputSource(first.isr);
11578     }
11579     if (second.isr != NULL) {
11580         RemoveInputSource(second.isr);
11581     }
11582
11583     ShutDownFrontEnd();
11584     exit(status);
11585 }
11586
11587 void
11588 PauseEvent()
11589 {
11590     if (appData.debugMode)
11591         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11592     if (pausing) {
11593         pausing = FALSE;
11594         ModeHighlight();
11595         if (gameMode == MachinePlaysWhite ||
11596             gameMode == MachinePlaysBlack) {
11597             StartClocks();
11598         } else {
11599             DisplayBothClocks();
11600         }
11601         if (gameMode == PlayFromGameFile) {
11602             if (appData.timeDelay >= 0)
11603                 AutoPlayGameLoop();
11604         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11605             Reset(FALSE, TRUE);
11606             SendToICS(ics_prefix);
11607             SendToICS("refresh\n");
11608         } else if (currentMove < forwardMostMove) {
11609             ForwardInner(forwardMostMove);
11610         }
11611         pauseExamInvalid = FALSE;
11612     } else {
11613         switch (gameMode) {
11614           default:
11615             return;
11616           case IcsExamining:
11617             pauseExamForwardMostMove = forwardMostMove;
11618             pauseExamInvalid = FALSE;
11619             /* fall through */
11620           case IcsObserving:
11621           case IcsPlayingWhite:
11622           case IcsPlayingBlack:
11623             pausing = TRUE;
11624             ModeHighlight();
11625             return;
11626           case PlayFromGameFile:
11627             (void) StopLoadGameTimer();
11628             pausing = TRUE;
11629             ModeHighlight();
11630             break;
11631           case BeginningOfGame:
11632             if (appData.icsActive) return;
11633             /* else fall through */
11634           case MachinePlaysWhite:
11635           case MachinePlaysBlack:
11636           case TwoMachinesPlay:
11637             if (forwardMostMove == 0)
11638               return;           /* don't pause if no one has moved */
11639             if ((gameMode == MachinePlaysWhite &&
11640                  !WhiteOnMove(forwardMostMove)) ||
11641                 (gameMode == MachinePlaysBlack &&
11642                  WhiteOnMove(forwardMostMove))) {
11643                 StopClocks();
11644             }
11645             pausing = TRUE;
11646             ModeHighlight();
11647             break;
11648         }
11649     }
11650 }
11651
11652 void
11653 EditCommentEvent()
11654 {
11655     char title[MSG_SIZ];
11656
11657     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11658       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11659     } else {
11660       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11661                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11662                parseList[currentMove - 1]);
11663     }
11664
11665     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11666 }
11667
11668
11669 void
11670 EditTagsEvent()
11671 {
11672     char *tags = PGNTags(&gameInfo);
11673     EditTagsPopUp(tags, NULL);
11674     free(tags);
11675 }
11676
11677 void
11678 AnalyzeModeEvent()
11679 {
11680     if (appData.noChessProgram || gameMode == AnalyzeMode)
11681       return;
11682
11683     if (gameMode != AnalyzeFile) {
11684         if (!appData.icsEngineAnalyze) {
11685                EditGameEvent();
11686                if (gameMode != EditGame) return;
11687         }
11688         ResurrectChessProgram();
11689         SendToProgram("analyze\n", &first);
11690         first.analyzing = TRUE;
11691         /*first.maybeThinking = TRUE;*/
11692         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11693         EngineOutputPopUp();
11694     }
11695     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11696     pausing = FALSE;
11697     ModeHighlight();
11698     SetGameInfo();
11699
11700     StartAnalysisClock();
11701     GetTimeMark(&lastNodeCountTime);
11702     lastNodeCount = 0;
11703 }
11704
11705 void
11706 AnalyzeFileEvent()
11707 {
11708     if (appData.noChessProgram || gameMode == AnalyzeFile)
11709       return;
11710
11711     if (gameMode != AnalyzeMode) {
11712         EditGameEvent();
11713         if (gameMode != EditGame) return;
11714         ResurrectChessProgram();
11715         SendToProgram("analyze\n", &first);
11716         first.analyzing = TRUE;
11717         /*first.maybeThinking = TRUE;*/
11718         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11719         EngineOutputPopUp();
11720     }
11721     gameMode = AnalyzeFile;
11722     pausing = FALSE;
11723     ModeHighlight();
11724     SetGameInfo();
11725
11726     StartAnalysisClock();
11727     GetTimeMark(&lastNodeCountTime);
11728     lastNodeCount = 0;
11729 }
11730
11731 void
11732 MachineWhiteEvent()
11733 {
11734     char buf[MSG_SIZ];
11735     char *bookHit = NULL;
11736
11737     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11738       return;
11739
11740
11741     if (gameMode == PlayFromGameFile ||
11742         gameMode == TwoMachinesPlay  ||
11743         gameMode == Training         ||
11744         gameMode == AnalyzeMode      ||
11745         gameMode == EndOfGame)
11746         EditGameEvent();
11747
11748     if (gameMode == EditPosition)
11749         EditPositionDone(TRUE);
11750
11751     if (!WhiteOnMove(currentMove)) {
11752         DisplayError(_("It is not White's turn"), 0);
11753         return;
11754     }
11755
11756     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11757       ExitAnalyzeMode();
11758
11759     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11760         gameMode == AnalyzeFile)
11761         TruncateGame();
11762
11763     ResurrectChessProgram();    /* in case it isn't running */
11764     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11765         gameMode = MachinePlaysWhite;
11766         ResetClocks();
11767     } else
11768     gameMode = MachinePlaysWhite;
11769     pausing = FALSE;
11770     ModeHighlight();
11771     SetGameInfo();
11772     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11773     DisplayTitle(buf);
11774     if (first.sendName) {
11775       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11776       SendToProgram(buf, &first);
11777     }
11778     if (first.sendTime) {
11779       if (first.useColors) {
11780         SendToProgram("black\n", &first); /*gnu kludge*/
11781       }
11782       SendTimeRemaining(&first, TRUE);
11783     }
11784     if (first.useColors) {
11785       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11786     }
11787     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11788     SetMachineThinkingEnables();
11789     first.maybeThinking = TRUE;
11790     StartClocks();
11791     firstMove = FALSE;
11792
11793     if (appData.autoFlipView && !flipView) {
11794       flipView = !flipView;
11795       DrawPosition(FALSE, NULL);
11796       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11797     }
11798
11799     if(bookHit) { // [HGM] book: simulate book reply
11800         static char bookMove[MSG_SIZ]; // a bit generous?
11801
11802         programStats.nodes = programStats.depth = programStats.time =
11803         programStats.score = programStats.got_only_move = 0;
11804         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11805
11806         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11807         strcat(bookMove, bookHit);
11808         HandleMachineMove(bookMove, &first);
11809     }
11810 }
11811
11812 void
11813 MachineBlackEvent()
11814 {
11815   char buf[MSG_SIZ];
11816   char *bookHit = NULL;
11817
11818     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11819         return;
11820
11821
11822     if (gameMode == PlayFromGameFile ||
11823         gameMode == TwoMachinesPlay  ||
11824         gameMode == Training         ||
11825         gameMode == AnalyzeMode      ||
11826         gameMode == EndOfGame)
11827         EditGameEvent();
11828
11829     if (gameMode == EditPosition)
11830         EditPositionDone(TRUE);
11831
11832     if (WhiteOnMove(currentMove)) {
11833         DisplayError(_("It is not Black's turn"), 0);
11834         return;
11835     }
11836
11837     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11838       ExitAnalyzeMode();
11839
11840     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11841         gameMode == AnalyzeFile)
11842         TruncateGame();
11843
11844     ResurrectChessProgram();    /* in case it isn't running */
11845     gameMode = MachinePlaysBlack;
11846     pausing = FALSE;
11847     ModeHighlight();
11848     SetGameInfo();
11849     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11850     DisplayTitle(buf);
11851     if (first.sendName) {
11852       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11853       SendToProgram(buf, &first);
11854     }
11855     if (first.sendTime) {
11856       if (first.useColors) {
11857         SendToProgram("white\n", &first); /*gnu kludge*/
11858       }
11859       SendTimeRemaining(&first, FALSE);
11860     }
11861     if (first.useColors) {
11862       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11863     }
11864     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11865     SetMachineThinkingEnables();
11866     first.maybeThinking = TRUE;
11867     StartClocks();
11868
11869     if (appData.autoFlipView && flipView) {
11870       flipView = !flipView;
11871       DrawPosition(FALSE, NULL);
11872       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11873     }
11874     if(bookHit) { // [HGM] book: simulate book reply
11875         static char bookMove[MSG_SIZ]; // a bit generous?
11876
11877         programStats.nodes = programStats.depth = programStats.time =
11878         programStats.score = programStats.got_only_move = 0;
11879         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11880
11881         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11882         strcat(bookMove, bookHit);
11883         HandleMachineMove(bookMove, &first);
11884     }
11885 }
11886
11887
11888 void
11889 DisplayTwoMachinesTitle()
11890 {
11891     char buf[MSG_SIZ];
11892     if (appData.matchGames > 0) {
11893         if (first.twoMachinesColor[0] == 'w') {
11894           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11895                    gameInfo.white, gameInfo.black,
11896                    first.matchWins, second.matchWins,
11897                    matchGame - 1 - (first.matchWins + second.matchWins));
11898         } else {
11899           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11900                    gameInfo.white, gameInfo.black,
11901                    second.matchWins, first.matchWins,
11902                    matchGame - 1 - (first.matchWins + second.matchWins));
11903         }
11904     } else {
11905       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11906     }
11907     DisplayTitle(buf);
11908 }
11909
11910 void
11911 SettingsMenuIfReady()
11912 {
11913   if (second.lastPing != second.lastPong) {
11914     DisplayMessage("", _("Waiting for second chess program"));
11915     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11916     return;
11917   }
11918   ThawUI();
11919   DisplayMessage("", "");
11920   SettingsPopUp(&second);
11921 }
11922
11923 int
11924 WaitForSecond(DelayedEventCallback retry)
11925 {
11926     if (second.pr == NULL) {
11927         StartChessProgram(&second);
11928         if (second.protocolVersion == 1) {
11929           retry();
11930         } else {
11931           /* kludge: allow timeout for initial "feature" command */
11932           FreezeUI();
11933           DisplayMessage("", _("Starting second chess program"));
11934           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11935         }
11936         return 1;
11937     }
11938     return 0;
11939 }
11940
11941 void
11942 TwoMachinesEvent P((void))
11943 {
11944     int i;
11945     char buf[MSG_SIZ];
11946     ChessProgramState *onmove;
11947     char *bookHit = NULL;
11948
11949     if (appData.noChessProgram) return;
11950
11951     switch (gameMode) {
11952       case TwoMachinesPlay:
11953         return;
11954       case MachinePlaysWhite:
11955       case MachinePlaysBlack:
11956         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11957             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11958             return;
11959         }
11960         /* fall through */
11961       case BeginningOfGame:
11962       case PlayFromGameFile:
11963       case EndOfGame:
11964         EditGameEvent();
11965         if (gameMode != EditGame) return;
11966         break;
11967       case EditPosition:
11968         EditPositionDone(TRUE);
11969         break;
11970       case AnalyzeMode:
11971       case AnalyzeFile:
11972         ExitAnalyzeMode();
11973         break;
11974       case EditGame:
11975       default:
11976         break;
11977     }
11978
11979 //    forwardMostMove = currentMove;
11980     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11981     ResurrectChessProgram();    /* in case first program isn't running */
11982
11983     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11984     DisplayMessage("", "");
11985     InitChessProgram(&second, FALSE);
11986     SendToProgram("force\n", &second);
11987     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11988       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11989       return;
11990     }
11991     if (startedFromSetupPosition) {
11992         SendBoard(&second, backwardMostMove);
11993     if (appData.debugMode) {
11994         fprintf(debugFP, "Two Machines\n");
11995     }
11996     }
11997     for (i = backwardMostMove; i < forwardMostMove; i++) {
11998         SendMoveToProgram(i, &second);
11999     }
12000
12001     gameMode = TwoMachinesPlay;
12002     pausing = FALSE;
12003     ModeHighlight();
12004     SetGameInfo();
12005     DisplayTwoMachinesTitle();
12006     firstMove = TRUE;
12007     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12008         onmove = &first;
12009     } else {
12010         onmove = &second;
12011     }
12012
12013     SendToProgram(first.computerString, &first);
12014     if (first.sendName) {
12015       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12016       SendToProgram(buf, &first);
12017     }
12018     SendToProgram(second.computerString, &second);
12019     if (second.sendName) {
12020       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12021       SendToProgram(buf, &second);
12022     }
12023
12024     ResetClocks();
12025     if (!first.sendTime || !second.sendTime) {
12026         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12027         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12028     }
12029     if (onmove->sendTime) {
12030       if (onmove->useColors) {
12031         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12032       }
12033       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12034     }
12035     if (onmove->useColors) {
12036       SendToProgram(onmove->twoMachinesColor, onmove);
12037     }
12038     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12039 //    SendToProgram("go\n", onmove);
12040     onmove->maybeThinking = TRUE;
12041     SetMachineThinkingEnables();
12042
12043     StartClocks();
12044
12045     if(bookHit) { // [HGM] book: simulate book reply
12046         static char bookMove[MSG_SIZ]; // a bit generous?
12047
12048         programStats.nodes = programStats.depth = programStats.time =
12049         programStats.score = programStats.got_only_move = 0;
12050         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12051
12052         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12053         strcat(bookMove, bookHit);
12054         savedMessage = bookMove; // args for deferred call
12055         savedState = onmove;
12056         ScheduleDelayedEvent(DeferredBookMove, 1);
12057     }
12058 }
12059
12060 void
12061 TrainingEvent()
12062 {
12063     if (gameMode == Training) {
12064       SetTrainingModeOff();
12065       gameMode = PlayFromGameFile;
12066       DisplayMessage("", _("Training mode off"));
12067     } else {
12068       gameMode = Training;
12069       animateTraining = appData.animate;
12070
12071       /* make sure we are not already at the end of the game */
12072       if (currentMove < forwardMostMove) {
12073         SetTrainingModeOn();
12074         DisplayMessage("", _("Training mode on"));
12075       } else {
12076         gameMode = PlayFromGameFile;
12077         DisplayError(_("Already at end of game"), 0);
12078       }
12079     }
12080     ModeHighlight();
12081 }
12082
12083 void
12084 IcsClientEvent()
12085 {
12086     if (!appData.icsActive) return;
12087     switch (gameMode) {
12088       case IcsPlayingWhite:
12089       case IcsPlayingBlack:
12090       case IcsObserving:
12091       case IcsIdle:
12092       case BeginningOfGame:
12093       case IcsExamining:
12094         return;
12095
12096       case EditGame:
12097         break;
12098
12099       case EditPosition:
12100         EditPositionDone(TRUE);
12101         break;
12102
12103       case AnalyzeMode:
12104       case AnalyzeFile:
12105         ExitAnalyzeMode();
12106         break;
12107
12108       default:
12109         EditGameEvent();
12110         break;
12111     }
12112
12113     gameMode = IcsIdle;
12114     ModeHighlight();
12115     return;
12116 }
12117
12118
12119 void
12120 EditGameEvent()
12121 {
12122     int i;
12123
12124     switch (gameMode) {
12125       case Training:
12126         SetTrainingModeOff();
12127         break;
12128       case MachinePlaysWhite:
12129       case MachinePlaysBlack:
12130       case BeginningOfGame:
12131         SendToProgram("force\n", &first);
12132         SetUserThinkingEnables();
12133         break;
12134       case PlayFromGameFile:
12135         (void) StopLoadGameTimer();
12136         if (gameFileFP != NULL) {
12137             gameFileFP = NULL;
12138         }
12139         break;
12140       case EditPosition:
12141         EditPositionDone(TRUE);
12142         break;
12143       case AnalyzeMode:
12144       case AnalyzeFile:
12145         ExitAnalyzeMode();
12146         SendToProgram("force\n", &first);
12147         break;
12148       case TwoMachinesPlay:
12149         GameEnds(EndOfFile, NULL, GE_PLAYER);
12150         ResurrectChessProgram();
12151         SetUserThinkingEnables();
12152         break;
12153       case EndOfGame:
12154         ResurrectChessProgram();
12155         break;
12156       case IcsPlayingBlack:
12157       case IcsPlayingWhite:
12158         DisplayError(_("Warning: You are still playing a game"), 0);
12159         break;
12160       case IcsObserving:
12161         DisplayError(_("Warning: You are still observing a game"), 0);
12162         break;
12163       case IcsExamining:
12164         DisplayError(_("Warning: You are still examining a game"), 0);
12165         break;
12166       case IcsIdle:
12167         break;
12168       case EditGame:
12169       default:
12170         return;
12171     }
12172
12173     pausing = FALSE;
12174     StopClocks();
12175     first.offeredDraw = second.offeredDraw = 0;
12176
12177     if (gameMode == PlayFromGameFile) {
12178         whiteTimeRemaining = timeRemaining[0][currentMove];
12179         blackTimeRemaining = timeRemaining[1][currentMove];
12180         DisplayTitle("");
12181     }
12182
12183     if (gameMode == MachinePlaysWhite ||
12184         gameMode == MachinePlaysBlack ||
12185         gameMode == TwoMachinesPlay ||
12186         gameMode == EndOfGame) {
12187         i = forwardMostMove;
12188         while (i > currentMove) {
12189             SendToProgram("undo\n", &first);
12190             i--;
12191         }
12192         whiteTimeRemaining = timeRemaining[0][currentMove];
12193         blackTimeRemaining = timeRemaining[1][currentMove];
12194         DisplayBothClocks();
12195         if (whiteFlag || blackFlag) {
12196             whiteFlag = blackFlag = 0;
12197         }
12198         DisplayTitle("");
12199     }
12200
12201     gameMode = EditGame;
12202     ModeHighlight();
12203     SetGameInfo();
12204 }
12205
12206
12207 void
12208 EditPositionEvent()
12209 {
12210     if (gameMode == EditPosition) {
12211         EditGameEvent();
12212         return;
12213     }
12214
12215     EditGameEvent();
12216     if (gameMode != EditGame) return;
12217
12218     gameMode = EditPosition;
12219     ModeHighlight();
12220     SetGameInfo();
12221     if (currentMove > 0)
12222       CopyBoard(boards[0], boards[currentMove]);
12223
12224     blackPlaysFirst = !WhiteOnMove(currentMove);
12225     ResetClocks();
12226     currentMove = forwardMostMove = backwardMostMove = 0;
12227     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12228     DisplayMove(-1);
12229 }
12230
12231 void
12232 ExitAnalyzeMode()
12233 {
12234     /* [DM] icsEngineAnalyze - possible call from other functions */
12235     if (appData.icsEngineAnalyze) {
12236         appData.icsEngineAnalyze = FALSE;
12237
12238         DisplayMessage("",_("Close ICS engine analyze..."));
12239     }
12240     if (first.analysisSupport && first.analyzing) {
12241       SendToProgram("exit\n", &first);
12242       first.analyzing = FALSE;
12243     }
12244     thinkOutput[0] = NULLCHAR;
12245 }
12246
12247 void
12248 EditPositionDone(Boolean fakeRights)
12249 {
12250     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12251
12252     startedFromSetupPosition = TRUE;
12253     InitChessProgram(&first, FALSE);
12254     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12255       boards[0][EP_STATUS] = EP_NONE;
12256       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12257     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12258         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12259         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12260       } else boards[0][CASTLING][2] = NoRights;
12261     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12262         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12263         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12264       } else boards[0][CASTLING][5] = NoRights;
12265     }
12266     SendToProgram("force\n", &first);
12267     if (blackPlaysFirst) {
12268         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12269         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12270         currentMove = forwardMostMove = backwardMostMove = 1;
12271         CopyBoard(boards[1], boards[0]);
12272     } else {
12273         currentMove = forwardMostMove = backwardMostMove = 0;
12274     }
12275     SendBoard(&first, forwardMostMove);
12276     if (appData.debugMode) {
12277         fprintf(debugFP, "EditPosDone\n");
12278     }
12279     DisplayTitle("");
12280     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12281     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12282     gameMode = EditGame;
12283     ModeHighlight();
12284     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12285     ClearHighlights(); /* [AS] */
12286 }
12287
12288 /* Pause for `ms' milliseconds */
12289 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12290 void
12291 TimeDelay(ms)
12292      long ms;
12293 {
12294     TimeMark m1, m2;
12295
12296     GetTimeMark(&m1);
12297     do {
12298         GetTimeMark(&m2);
12299     } while (SubtractTimeMarks(&m2, &m1) < ms);
12300 }
12301
12302 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12303 void
12304 SendMultiLineToICS(buf)
12305      char *buf;
12306 {
12307     char temp[MSG_SIZ+1], *p;
12308     int len;
12309
12310     len = strlen(buf);
12311     if (len > MSG_SIZ)
12312       len = MSG_SIZ;
12313
12314     strncpy(temp, buf, len);
12315     temp[len] = 0;
12316
12317     p = temp;
12318     while (*p) {
12319         if (*p == '\n' || *p == '\r')
12320           *p = ' ';
12321         ++p;
12322     }
12323
12324     strcat(temp, "\n");
12325     SendToICS(temp);
12326     SendToPlayer(temp, strlen(temp));
12327 }
12328
12329 void
12330 SetWhiteToPlayEvent()
12331 {
12332     if (gameMode == EditPosition) {
12333         blackPlaysFirst = FALSE;
12334         DisplayBothClocks();    /* works because currentMove is 0 */
12335     } else if (gameMode == IcsExamining) {
12336         SendToICS(ics_prefix);
12337         SendToICS("tomove white\n");
12338     }
12339 }
12340
12341 void
12342 SetBlackToPlayEvent()
12343 {
12344     if (gameMode == EditPosition) {
12345         blackPlaysFirst = TRUE;
12346         currentMove = 1;        /* kludge */
12347         DisplayBothClocks();
12348         currentMove = 0;
12349     } else if (gameMode == IcsExamining) {
12350         SendToICS(ics_prefix);
12351         SendToICS("tomove black\n");
12352     }
12353 }
12354
12355 void
12356 EditPositionMenuEvent(selection, x, y)
12357      ChessSquare selection;
12358      int x, y;
12359 {
12360     char buf[MSG_SIZ];
12361     ChessSquare piece = boards[0][y][x];
12362
12363     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12364
12365     switch (selection) {
12366       case ClearBoard:
12367         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12368             SendToICS(ics_prefix);
12369             SendToICS("bsetup clear\n");
12370         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12371             SendToICS(ics_prefix);
12372             SendToICS("clearboard\n");
12373         } else {
12374             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12375                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12376                 for (y = 0; y < BOARD_HEIGHT; y++) {
12377                     if (gameMode == IcsExamining) {
12378                         if (boards[currentMove][y][x] != EmptySquare) {
12379                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12380                                     AAA + x, ONE + y);
12381                             SendToICS(buf);
12382                         }
12383                     } else {
12384                         boards[0][y][x] = p;
12385                     }
12386                 }
12387             }
12388         }
12389         if (gameMode == EditPosition) {
12390             DrawPosition(FALSE, boards[0]);
12391         }
12392         break;
12393
12394       case WhitePlay:
12395         SetWhiteToPlayEvent();
12396         break;
12397
12398       case BlackPlay:
12399         SetBlackToPlayEvent();
12400         break;
12401
12402       case EmptySquare:
12403         if (gameMode == IcsExamining) {
12404             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12405             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12406             SendToICS(buf);
12407         } else {
12408             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12409                 if(x == BOARD_LEFT-2) {
12410                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12411                     boards[0][y][1] = 0;
12412                 } else
12413                 if(x == BOARD_RGHT+1) {
12414                     if(y >= gameInfo.holdingsSize) break;
12415                     boards[0][y][BOARD_WIDTH-2] = 0;
12416                 } else break;
12417             }
12418             boards[0][y][x] = EmptySquare;
12419             DrawPosition(FALSE, boards[0]);
12420         }
12421         break;
12422
12423       case PromotePiece:
12424         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12425            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12426             selection = (ChessSquare) (PROMOTED piece);
12427         } else if(piece == EmptySquare) selection = WhiteSilver;
12428         else selection = (ChessSquare)((int)piece - 1);
12429         goto defaultlabel;
12430
12431       case DemotePiece:
12432         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12433            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12434             selection = (ChessSquare) (DEMOTED piece);
12435         } else if(piece == EmptySquare) selection = BlackSilver;
12436         else selection = (ChessSquare)((int)piece + 1);
12437         goto defaultlabel;
12438
12439       case WhiteQueen:
12440       case BlackQueen:
12441         if(gameInfo.variant == VariantShatranj ||
12442            gameInfo.variant == VariantXiangqi  ||
12443            gameInfo.variant == VariantCourier  ||
12444            gameInfo.variant == VariantMakruk     )
12445             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12446         goto defaultlabel;
12447
12448       case WhiteKing:
12449       case BlackKing:
12450         if(gameInfo.variant == VariantXiangqi)
12451             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12452         if(gameInfo.variant == VariantKnightmate)
12453             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12454       default:
12455         defaultlabel:
12456         if (gameMode == IcsExamining) {
12457             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12458             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12459                      PieceToChar(selection), AAA + x, ONE + y);
12460             SendToICS(buf);
12461         } else {
12462             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12463                 int n;
12464                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12465                     n = PieceToNumber(selection - BlackPawn);
12466                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12467                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12468                     boards[0][BOARD_HEIGHT-1-n][1]++;
12469                 } else
12470                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12471                     n = PieceToNumber(selection);
12472                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12473                     boards[0][n][BOARD_WIDTH-1] = selection;
12474                     boards[0][n][BOARD_WIDTH-2]++;
12475                 }
12476             } else
12477             boards[0][y][x] = selection;
12478             DrawPosition(TRUE, boards[0]);
12479         }
12480         break;
12481     }
12482 }
12483
12484
12485 void
12486 DropMenuEvent(selection, x, y)
12487      ChessSquare selection;
12488      int x, y;
12489 {
12490     ChessMove moveType;
12491
12492     switch (gameMode) {
12493       case IcsPlayingWhite:
12494       case MachinePlaysBlack:
12495         if (!WhiteOnMove(currentMove)) {
12496             DisplayMoveError(_("It is Black's turn"));
12497             return;
12498         }
12499         moveType = WhiteDrop;
12500         break;
12501       case IcsPlayingBlack:
12502       case MachinePlaysWhite:
12503         if (WhiteOnMove(currentMove)) {
12504             DisplayMoveError(_("It is White's turn"));
12505             return;
12506         }
12507         moveType = BlackDrop;
12508         break;
12509       case EditGame:
12510         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12511         break;
12512       default:
12513         return;
12514     }
12515
12516     if (moveType == BlackDrop && selection < BlackPawn) {
12517       selection = (ChessSquare) ((int) selection
12518                                  + (int) BlackPawn - (int) WhitePawn);
12519     }
12520     if (boards[currentMove][y][x] != EmptySquare) {
12521         DisplayMoveError(_("That square is occupied"));
12522         return;
12523     }
12524
12525     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12526 }
12527
12528 void
12529 AcceptEvent()
12530 {
12531     /* Accept a pending offer of any kind from opponent */
12532
12533     if (appData.icsActive) {
12534         SendToICS(ics_prefix);
12535         SendToICS("accept\n");
12536     } else if (cmailMsgLoaded) {
12537         if (currentMove == cmailOldMove &&
12538             commentList[cmailOldMove] != NULL &&
12539             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12540                    "Black offers a draw" : "White offers a draw")) {
12541             TruncateGame();
12542             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12543             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12544         } else {
12545             DisplayError(_("There is no pending offer on this move"), 0);
12546             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12547         }
12548     } else {
12549         /* Not used for offers from chess program */
12550     }
12551 }
12552
12553 void
12554 DeclineEvent()
12555 {
12556     /* Decline a pending offer of any kind from opponent */
12557
12558     if (appData.icsActive) {
12559         SendToICS(ics_prefix);
12560         SendToICS("decline\n");
12561     } else if (cmailMsgLoaded) {
12562         if (currentMove == cmailOldMove &&
12563             commentList[cmailOldMove] != NULL &&
12564             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12565                    "Black offers a draw" : "White offers a draw")) {
12566 #ifdef NOTDEF
12567             AppendComment(cmailOldMove, "Draw declined", TRUE);
12568             DisplayComment(cmailOldMove - 1, "Draw declined");
12569 #endif /*NOTDEF*/
12570         } else {
12571             DisplayError(_("There is no pending offer on this move"), 0);
12572         }
12573     } else {
12574         /* Not used for offers from chess program */
12575     }
12576 }
12577
12578 void
12579 RematchEvent()
12580 {
12581     /* Issue ICS rematch command */
12582     if (appData.icsActive) {
12583         SendToICS(ics_prefix);
12584         SendToICS("rematch\n");
12585     }
12586 }
12587
12588 void
12589 CallFlagEvent()
12590 {
12591     /* Call your opponent's flag (claim a win on time) */
12592     if (appData.icsActive) {
12593         SendToICS(ics_prefix);
12594         SendToICS("flag\n");
12595     } else {
12596         switch (gameMode) {
12597           default:
12598             return;
12599           case MachinePlaysWhite:
12600             if (whiteFlag) {
12601                 if (blackFlag)
12602                   GameEnds(GameIsDrawn, "Both players ran out of time",
12603                            GE_PLAYER);
12604                 else
12605                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12606             } else {
12607                 DisplayError(_("Your opponent is not out of time"), 0);
12608             }
12609             break;
12610           case MachinePlaysBlack:
12611             if (blackFlag) {
12612                 if (whiteFlag)
12613                   GameEnds(GameIsDrawn, "Both players ran out of time",
12614                            GE_PLAYER);
12615                 else
12616                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12617             } else {
12618                 DisplayError(_("Your opponent is not out of time"), 0);
12619             }
12620             break;
12621         }
12622     }
12623 }
12624
12625 void
12626 DrawEvent()
12627 {
12628     /* Offer draw or accept pending draw offer from opponent */
12629
12630     if (appData.icsActive) {
12631         /* Note: tournament rules require draw offers to be
12632            made after you make your move but before you punch
12633            your clock.  Currently ICS doesn't let you do that;
12634            instead, you immediately punch your clock after making
12635            a move, but you can offer a draw at any time. */
12636
12637         SendToICS(ics_prefix);
12638         SendToICS("draw\n");
12639         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12640     } else if (cmailMsgLoaded) {
12641         if (currentMove == cmailOldMove &&
12642             commentList[cmailOldMove] != NULL &&
12643             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12644                    "Black offers a draw" : "White offers a draw")) {
12645             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12646             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12647         } else if (currentMove == cmailOldMove + 1) {
12648             char *offer = WhiteOnMove(cmailOldMove) ?
12649               "White offers a draw" : "Black offers a draw";
12650             AppendComment(currentMove, offer, TRUE);
12651             DisplayComment(currentMove - 1, offer);
12652             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12653         } else {
12654             DisplayError(_("You must make your move before offering a draw"), 0);
12655             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12656         }
12657     } else if (first.offeredDraw) {
12658         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12659     } else {
12660         if (first.sendDrawOffers) {
12661             SendToProgram("draw\n", &first);
12662             userOfferedDraw = TRUE;
12663         }
12664     }
12665 }
12666
12667 void
12668 AdjournEvent()
12669 {
12670     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12671
12672     if (appData.icsActive) {
12673         SendToICS(ics_prefix);
12674         SendToICS("adjourn\n");
12675     } else {
12676         /* Currently GNU Chess doesn't offer or accept Adjourns */
12677     }
12678 }
12679
12680
12681 void
12682 AbortEvent()
12683 {
12684     /* Offer Abort or accept pending Abort offer from opponent */
12685
12686     if (appData.icsActive) {
12687         SendToICS(ics_prefix);
12688         SendToICS("abort\n");
12689     } else {
12690         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12691     }
12692 }
12693
12694 void
12695 ResignEvent()
12696 {
12697     /* Resign.  You can do this even if it's not your turn. */
12698
12699     if (appData.icsActive) {
12700         SendToICS(ics_prefix);
12701         SendToICS("resign\n");
12702     } else {
12703         switch (gameMode) {
12704           case MachinePlaysWhite:
12705             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12706             break;
12707           case MachinePlaysBlack:
12708             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12709             break;
12710           case EditGame:
12711             if (cmailMsgLoaded) {
12712                 TruncateGame();
12713                 if (WhiteOnMove(cmailOldMove)) {
12714                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12715                 } else {
12716                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12717                 }
12718                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12719             }
12720             break;
12721           default:
12722             break;
12723         }
12724     }
12725 }
12726
12727
12728 void
12729 StopObservingEvent()
12730 {
12731     /* Stop observing current games */
12732     SendToICS(ics_prefix);
12733     SendToICS("unobserve\n");
12734 }
12735
12736 void
12737 StopExaminingEvent()
12738 {
12739     /* Stop observing current game */
12740     SendToICS(ics_prefix);
12741     SendToICS("unexamine\n");
12742 }
12743
12744 void
12745 ForwardInner(target)
12746      int target;
12747 {
12748     int limit;
12749
12750     if (appData.debugMode)
12751         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12752                 target, currentMove, forwardMostMove);
12753
12754     if (gameMode == EditPosition)
12755       return;
12756
12757     if (gameMode == PlayFromGameFile && !pausing)
12758       PauseEvent();
12759
12760     if (gameMode == IcsExamining && pausing)
12761       limit = pauseExamForwardMostMove;
12762     else
12763       limit = forwardMostMove;
12764
12765     if (target > limit) target = limit;
12766
12767     if (target > 0 && moveList[target - 1][0]) {
12768         int fromX, fromY, toX, toY;
12769         toX = moveList[target - 1][2] - AAA;
12770         toY = moveList[target - 1][3] - ONE;
12771         if (moveList[target - 1][1] == '@') {
12772             if (appData.highlightLastMove) {
12773                 SetHighlights(-1, -1, toX, toY);
12774             }
12775         } else {
12776             fromX = moveList[target - 1][0] - AAA;
12777             fromY = moveList[target - 1][1] - ONE;
12778             if (target == currentMove + 1) {
12779                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12780             }
12781             if (appData.highlightLastMove) {
12782                 SetHighlights(fromX, fromY, toX, toY);
12783             }
12784         }
12785     }
12786     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12787         gameMode == Training || gameMode == PlayFromGameFile ||
12788         gameMode == AnalyzeFile) {
12789         while (currentMove < target) {
12790             SendMoveToProgram(currentMove++, &first);
12791         }
12792     } else {
12793         currentMove = target;
12794     }
12795
12796     if (gameMode == EditGame || gameMode == EndOfGame) {
12797         whiteTimeRemaining = timeRemaining[0][currentMove];
12798         blackTimeRemaining = timeRemaining[1][currentMove];
12799     }
12800     DisplayBothClocks();
12801     DisplayMove(currentMove - 1);
12802     DrawPosition(FALSE, boards[currentMove]);
12803     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12804     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12805         DisplayComment(currentMove - 1, commentList[currentMove]);
12806     }
12807 }
12808
12809
12810 void
12811 ForwardEvent()
12812 {
12813     if (gameMode == IcsExamining && !pausing) {
12814         SendToICS(ics_prefix);
12815         SendToICS("forward\n");
12816     } else {
12817         ForwardInner(currentMove + 1);
12818     }
12819 }
12820
12821 void
12822 ToEndEvent()
12823 {
12824     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12825         /* to optimze, we temporarily turn off analysis mode while we feed
12826          * the remaining moves to the engine. Otherwise we get analysis output
12827          * after each move.
12828          */
12829         if (first.analysisSupport) {
12830           SendToProgram("exit\nforce\n", &first);
12831           first.analyzing = FALSE;
12832         }
12833     }
12834
12835     if (gameMode == IcsExamining && !pausing) {
12836         SendToICS(ics_prefix);
12837         SendToICS("forward 999999\n");
12838     } else {
12839         ForwardInner(forwardMostMove);
12840     }
12841
12842     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12843         /* we have fed all the moves, so reactivate analysis mode */
12844         SendToProgram("analyze\n", &first);
12845         first.analyzing = TRUE;
12846         /*first.maybeThinking = TRUE;*/
12847         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12848     }
12849 }
12850
12851 void
12852 BackwardInner(target)
12853      int target;
12854 {
12855     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12856
12857     if (appData.debugMode)
12858         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12859                 target, currentMove, forwardMostMove);
12860
12861     if (gameMode == EditPosition) return;
12862     if (currentMove <= backwardMostMove) {
12863         ClearHighlights();
12864         DrawPosition(full_redraw, boards[currentMove]);
12865         return;
12866     }
12867     if (gameMode == PlayFromGameFile && !pausing)
12868       PauseEvent();
12869
12870     if (moveList[target][0]) {
12871         int fromX, fromY, toX, toY;
12872         toX = moveList[target][2] - AAA;
12873         toY = moveList[target][3] - ONE;
12874         if (moveList[target][1] == '@') {
12875             if (appData.highlightLastMove) {
12876                 SetHighlights(-1, -1, toX, toY);
12877             }
12878         } else {
12879             fromX = moveList[target][0] - AAA;
12880             fromY = moveList[target][1] - ONE;
12881             if (target == currentMove - 1) {
12882                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12883             }
12884             if (appData.highlightLastMove) {
12885                 SetHighlights(fromX, fromY, toX, toY);
12886             }
12887         }
12888     }
12889     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12890         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12891         while (currentMove > target) {
12892             SendToProgram("undo\n", &first);
12893             currentMove--;
12894         }
12895     } else {
12896         currentMove = target;
12897     }
12898
12899     if (gameMode == EditGame || gameMode == EndOfGame) {
12900         whiteTimeRemaining = timeRemaining[0][currentMove];
12901         blackTimeRemaining = timeRemaining[1][currentMove];
12902     }
12903     DisplayBothClocks();
12904     DisplayMove(currentMove - 1);
12905     DrawPosition(full_redraw, boards[currentMove]);
12906     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12907     // [HGM] PV info: routine tests if comment empty
12908     DisplayComment(currentMove - 1, commentList[currentMove]);
12909 }
12910
12911 void
12912 BackwardEvent()
12913 {
12914     if (gameMode == IcsExamining && !pausing) {
12915         SendToICS(ics_prefix);
12916         SendToICS("backward\n");
12917     } else {
12918         BackwardInner(currentMove - 1);
12919     }
12920 }
12921
12922 void
12923 ToStartEvent()
12924 {
12925     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12926         /* to optimize, we temporarily turn off analysis mode while we undo
12927          * all the moves. Otherwise we get analysis output after each undo.
12928          */
12929         if (first.analysisSupport) {
12930           SendToProgram("exit\nforce\n", &first);
12931           first.analyzing = FALSE;
12932         }
12933     }
12934
12935     if (gameMode == IcsExamining && !pausing) {
12936         SendToICS(ics_prefix);
12937         SendToICS("backward 999999\n");
12938     } else {
12939         BackwardInner(backwardMostMove);
12940     }
12941
12942     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12943         /* we have fed all the moves, so reactivate analysis mode */
12944         SendToProgram("analyze\n", &first);
12945         first.analyzing = TRUE;
12946         /*first.maybeThinking = TRUE;*/
12947         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12948     }
12949 }
12950
12951 void
12952 ToNrEvent(int to)
12953 {
12954   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12955   if (to >= forwardMostMove) to = forwardMostMove;
12956   if (to <= backwardMostMove) to = backwardMostMove;
12957   if (to < currentMove) {
12958     BackwardInner(to);
12959   } else {
12960     ForwardInner(to);
12961   }
12962 }
12963
12964 void
12965 RevertEvent(Boolean annotate)
12966 {
12967     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12968         return;
12969     }
12970     if (gameMode != IcsExamining) {
12971         DisplayError(_("You are not examining a game"), 0);
12972         return;
12973     }
12974     if (pausing) {
12975         DisplayError(_("You can't revert while pausing"), 0);
12976         return;
12977     }
12978     SendToICS(ics_prefix);
12979     SendToICS("revert\n");
12980 }
12981
12982 void
12983 RetractMoveEvent()
12984 {
12985     switch (gameMode) {
12986       case MachinePlaysWhite:
12987       case MachinePlaysBlack:
12988         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12989             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12990             return;
12991         }
12992         if (forwardMostMove < 2) return;
12993         currentMove = forwardMostMove = forwardMostMove - 2;
12994         whiteTimeRemaining = timeRemaining[0][currentMove];
12995         blackTimeRemaining = timeRemaining[1][currentMove];
12996         DisplayBothClocks();
12997         DisplayMove(currentMove - 1);
12998         ClearHighlights();/*!! could figure this out*/
12999         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13000         SendToProgram("remove\n", &first);
13001         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13002         break;
13003
13004       case BeginningOfGame:
13005       default:
13006         break;
13007
13008       case IcsPlayingWhite:
13009       case IcsPlayingBlack:
13010         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13011             SendToICS(ics_prefix);
13012             SendToICS("takeback 2\n");
13013         } else {
13014             SendToICS(ics_prefix);
13015             SendToICS("takeback 1\n");
13016         }
13017         break;
13018     }
13019 }
13020
13021 void
13022 MoveNowEvent()
13023 {
13024     ChessProgramState *cps;
13025
13026     switch (gameMode) {
13027       case MachinePlaysWhite:
13028         if (!WhiteOnMove(forwardMostMove)) {
13029             DisplayError(_("It is your turn"), 0);
13030             return;
13031         }
13032         cps = &first;
13033         break;
13034       case MachinePlaysBlack:
13035         if (WhiteOnMove(forwardMostMove)) {
13036             DisplayError(_("It is your turn"), 0);
13037             return;
13038         }
13039         cps = &first;
13040         break;
13041       case TwoMachinesPlay:
13042         if (WhiteOnMove(forwardMostMove) ==
13043             (first.twoMachinesColor[0] == 'w')) {
13044             cps = &first;
13045         } else {
13046             cps = &second;
13047         }
13048         break;
13049       case BeginningOfGame:
13050       default:
13051         return;
13052     }
13053     SendToProgram("?\n", cps);
13054 }
13055
13056 void
13057 TruncateGameEvent()
13058 {
13059     EditGameEvent();
13060     if (gameMode != EditGame) return;
13061     TruncateGame();
13062 }
13063
13064 void
13065 TruncateGame()
13066 {
13067     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13068     if (forwardMostMove > currentMove) {
13069         if (gameInfo.resultDetails != NULL) {
13070             free(gameInfo.resultDetails);
13071             gameInfo.resultDetails = NULL;
13072             gameInfo.result = GameUnfinished;
13073         }
13074         forwardMostMove = currentMove;
13075         HistorySet(parseList, backwardMostMove, forwardMostMove,
13076                    currentMove-1);
13077     }
13078 }
13079
13080 void
13081 HintEvent()
13082 {
13083     if (appData.noChessProgram) return;
13084     switch (gameMode) {
13085       case MachinePlaysWhite:
13086         if (WhiteOnMove(forwardMostMove)) {
13087             DisplayError(_("Wait until your turn"), 0);
13088             return;
13089         }
13090         break;
13091       case BeginningOfGame:
13092       case MachinePlaysBlack:
13093         if (!WhiteOnMove(forwardMostMove)) {
13094             DisplayError(_("Wait until your turn"), 0);
13095             return;
13096         }
13097         break;
13098       default:
13099         DisplayError(_("No hint available"), 0);
13100         return;
13101     }
13102     SendToProgram("hint\n", &first);
13103     hintRequested = TRUE;
13104 }
13105
13106 void
13107 BookEvent()
13108 {
13109     if (appData.noChessProgram) return;
13110     switch (gameMode) {
13111       case MachinePlaysWhite:
13112         if (WhiteOnMove(forwardMostMove)) {
13113             DisplayError(_("Wait until your turn"), 0);
13114             return;
13115         }
13116         break;
13117       case BeginningOfGame:
13118       case MachinePlaysBlack:
13119         if (!WhiteOnMove(forwardMostMove)) {
13120             DisplayError(_("Wait until your turn"), 0);
13121             return;
13122         }
13123         break;
13124       case EditPosition:
13125         EditPositionDone(TRUE);
13126         break;
13127       case TwoMachinesPlay:
13128         return;
13129       default:
13130         break;
13131     }
13132     SendToProgram("bk\n", &first);
13133     bookOutput[0] = NULLCHAR;
13134     bookRequested = TRUE;
13135 }
13136
13137 void
13138 AboutGameEvent()
13139 {
13140     char *tags = PGNTags(&gameInfo);
13141     TagsPopUp(tags, CmailMsg());
13142     free(tags);
13143 }
13144
13145 /* end button procedures */
13146
13147 void
13148 PrintPosition(fp, move)
13149      FILE *fp;
13150      int move;
13151 {
13152     int i, j;
13153
13154     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13155         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13156             char c = PieceToChar(boards[move][i][j]);
13157             fputc(c == 'x' ? '.' : c, fp);
13158             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13159         }
13160     }
13161     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13162       fprintf(fp, "white to play\n");
13163     else
13164       fprintf(fp, "black to play\n");
13165 }
13166
13167 void
13168 PrintOpponents(fp)
13169      FILE *fp;
13170 {
13171     if (gameInfo.white != NULL) {
13172         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13173     } else {
13174         fprintf(fp, "\n");
13175     }
13176 }
13177
13178 /* Find last component of program's own name, using some heuristics */
13179 void
13180 TidyProgramName(prog, host, buf)
13181      char *prog, *host, buf[MSG_SIZ];
13182 {
13183     char *p, *q;
13184     int local = (strcmp(host, "localhost") == 0);
13185     while (!local && (p = strchr(prog, ';')) != NULL) {
13186         p++;
13187         while (*p == ' ') p++;
13188         prog = p;
13189     }
13190     if (*prog == '"' || *prog == '\'') {
13191         q = strchr(prog + 1, *prog);
13192     } else {
13193         q = strchr(prog, ' ');
13194     }
13195     if (q == NULL) q = prog + strlen(prog);
13196     p = q;
13197     while (p >= prog && *p != '/' && *p != '\\') p--;
13198     p++;
13199     if(p == prog && *p == '"') p++;
13200     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13201     memcpy(buf, p, q - p);
13202     buf[q - p] = NULLCHAR;
13203     if (!local) {
13204         strcat(buf, "@");
13205         strcat(buf, host);
13206     }
13207 }
13208
13209 char *
13210 TimeControlTagValue()
13211 {
13212     char buf[MSG_SIZ];
13213     if (!appData.clockMode) {
13214       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13215     } else if (movesPerSession > 0) {
13216       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13217     } else if (timeIncrement == 0) {
13218       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13219     } else {
13220       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13221     }
13222     return StrSave(buf);
13223 }
13224
13225 void
13226 SetGameInfo()
13227 {
13228     /* This routine is used only for certain modes */
13229     VariantClass v = gameInfo.variant;
13230     ChessMove r = GameUnfinished;
13231     char *p = NULL;
13232
13233     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13234         r = gameInfo.result;
13235         p = gameInfo.resultDetails;
13236         gameInfo.resultDetails = NULL;
13237     }
13238     ClearGameInfo(&gameInfo);
13239     gameInfo.variant = v;
13240
13241     switch (gameMode) {
13242       case MachinePlaysWhite:
13243         gameInfo.event = StrSave( appData.pgnEventHeader );
13244         gameInfo.site = StrSave(HostName());
13245         gameInfo.date = PGNDate();
13246         gameInfo.round = StrSave("-");
13247         gameInfo.white = StrSave(first.tidy);
13248         gameInfo.black = StrSave(UserName());
13249         gameInfo.timeControl = TimeControlTagValue();
13250         break;
13251
13252       case MachinePlaysBlack:
13253         gameInfo.event = StrSave( appData.pgnEventHeader );
13254         gameInfo.site = StrSave(HostName());
13255         gameInfo.date = PGNDate();
13256         gameInfo.round = StrSave("-");
13257         gameInfo.white = StrSave(UserName());
13258         gameInfo.black = StrSave(first.tidy);
13259         gameInfo.timeControl = TimeControlTagValue();
13260         break;
13261
13262       case TwoMachinesPlay:
13263         gameInfo.event = StrSave( appData.pgnEventHeader );
13264         gameInfo.site = StrSave(HostName());
13265         gameInfo.date = PGNDate();
13266         if (matchGame > 0) {
13267             char buf[MSG_SIZ];
13268             snprintf(buf, MSG_SIZ, "%d", matchGame);
13269             gameInfo.round = StrSave(buf);
13270         } else {
13271             gameInfo.round = StrSave("-");
13272         }
13273         if (first.twoMachinesColor[0] == 'w') {
13274             gameInfo.white = StrSave(first.tidy);
13275             gameInfo.black = StrSave(second.tidy);
13276         } else {
13277             gameInfo.white = StrSave(second.tidy);
13278             gameInfo.black = StrSave(first.tidy);
13279         }
13280         gameInfo.timeControl = TimeControlTagValue();
13281         break;
13282
13283       case EditGame:
13284         gameInfo.event = StrSave("Edited game");
13285         gameInfo.site = StrSave(HostName());
13286         gameInfo.date = PGNDate();
13287         gameInfo.round = StrSave("-");
13288         gameInfo.white = StrSave("-");
13289         gameInfo.black = StrSave("-");
13290         gameInfo.result = r;
13291         gameInfo.resultDetails = p;
13292         break;
13293
13294       case EditPosition:
13295         gameInfo.event = StrSave("Edited position");
13296         gameInfo.site = StrSave(HostName());
13297         gameInfo.date = PGNDate();
13298         gameInfo.round = StrSave("-");
13299         gameInfo.white = StrSave("-");
13300         gameInfo.black = StrSave("-");
13301         break;
13302
13303       case IcsPlayingWhite:
13304       case IcsPlayingBlack:
13305       case IcsObserving:
13306       case IcsExamining:
13307         break;
13308
13309       case PlayFromGameFile:
13310         gameInfo.event = StrSave("Game from non-PGN file");
13311         gameInfo.site = StrSave(HostName());
13312         gameInfo.date = PGNDate();
13313         gameInfo.round = StrSave("-");
13314         gameInfo.white = StrSave("?");
13315         gameInfo.black = StrSave("?");
13316         break;
13317
13318       default:
13319         break;
13320     }
13321 }
13322
13323 void
13324 ReplaceComment(index, text)
13325      int index;
13326      char *text;
13327 {
13328     int len;
13329     char *p;
13330     float score;
13331
13332     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13333        pvInfoList[index-1].depth == len &&
13334        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13335        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13336     while (*text == '\n') text++;
13337     len = strlen(text);
13338     while (len > 0 && text[len - 1] == '\n') len--;
13339
13340     if (commentList[index] != NULL)
13341       free(commentList[index]);
13342
13343     if (len == 0) {
13344         commentList[index] = NULL;
13345         return;
13346     }
13347   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13348       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13349       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13350     commentList[index] = (char *) malloc(len + 2);
13351     strncpy(commentList[index], text, len);
13352     commentList[index][len] = '\n';
13353     commentList[index][len + 1] = NULLCHAR;
13354   } else {
13355     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13356     char *p;
13357     commentList[index] = (char *) malloc(len + 7);
13358     safeStrCpy(commentList[index], "{\n", 3);
13359     safeStrCpy(commentList[index]+2, text, len+1);
13360     commentList[index][len+2] = NULLCHAR;
13361     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13362     strcat(commentList[index], "\n}\n");
13363   }
13364 }
13365
13366 void
13367 CrushCRs(text)
13368      char *text;
13369 {
13370   char *p = text;
13371   char *q = text;
13372   char ch;
13373
13374   do {
13375     ch = *p++;
13376     if (ch == '\r') continue;
13377     *q++ = ch;
13378   } while (ch != '\0');
13379 }
13380
13381 void
13382 AppendComment(index, text, addBraces)
13383      int index;
13384      char *text;
13385      Boolean addBraces; // [HGM] braces: tells if we should add {}
13386 {
13387     int oldlen, len;
13388     char *old;
13389
13390 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13391     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13392
13393     CrushCRs(text);
13394     while (*text == '\n') text++;
13395     len = strlen(text);
13396     while (len > 0 && text[len - 1] == '\n') len--;
13397
13398     if (len == 0) return;
13399
13400     if (commentList[index] != NULL) {
13401         old = commentList[index];
13402         oldlen = strlen(old);
13403         while(commentList[index][oldlen-1] ==  '\n')
13404           commentList[index][--oldlen] = NULLCHAR;
13405         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13406         safeStrCpy(commentList[index], old, oldlen + len + 6);
13407         free(old);
13408         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13409         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13410           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13411           while (*text == '\n') { text++; len--; }
13412           commentList[index][--oldlen] = NULLCHAR;
13413       }
13414         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13415         else          strcat(commentList[index], "\n");
13416         strcat(commentList[index], text);
13417         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13418         else          strcat(commentList[index], "\n");
13419     } else {
13420         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13421         if(addBraces)
13422           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13423         else commentList[index][0] = NULLCHAR;
13424         strcat(commentList[index], text);
13425         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13426         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13427     }
13428 }
13429
13430 static char * FindStr( char * text, char * sub_text )
13431 {
13432     char * result = strstr( text, sub_text );
13433
13434     if( result != NULL ) {
13435         result += strlen( sub_text );
13436     }
13437
13438     return result;
13439 }
13440
13441 /* [AS] Try to extract PV info from PGN comment */
13442 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13443 char *GetInfoFromComment( int index, char * text )
13444 {
13445     char * sep = text, *p;
13446
13447     if( text != NULL && index > 0 ) {
13448         int score = 0;
13449         int depth = 0;
13450         int time = -1, sec = 0, deci;
13451         char * s_eval = FindStr( text, "[%eval " );
13452         char * s_emt = FindStr( text, "[%emt " );
13453
13454         if( s_eval != NULL || s_emt != NULL ) {
13455             /* New style */
13456             char delim;
13457
13458             if( s_eval != NULL ) {
13459                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13460                     return text;
13461                 }
13462
13463                 if( delim != ']' ) {
13464                     return text;
13465                 }
13466             }
13467
13468             if( s_emt != NULL ) {
13469             }
13470                 return text;
13471         }
13472         else {
13473             /* We expect something like: [+|-]nnn.nn/dd */
13474             int score_lo = 0;
13475
13476             if(*text != '{') return text; // [HGM] braces: must be normal comment
13477
13478             sep = strchr( text, '/' );
13479             if( sep == NULL || sep < (text+4) ) {
13480                 return text;
13481             }
13482
13483             p = text;
13484             if(p[1] == '(') { // comment starts with PV
13485                p = strchr(p, ')'); // locate end of PV
13486                if(p == NULL || sep < p+5) return text;
13487                // at this point we have something like "{(.*) +0.23/6 ..."
13488                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13489                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13490                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13491             }
13492             time = -1; sec = -1; deci = -1;
13493             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13494                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13495                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13496                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13497                 return text;
13498             }
13499
13500             if( score_lo < 0 || score_lo >= 100 ) {
13501                 return text;
13502             }
13503
13504             if(sec >= 0) time = 600*time + 10*sec; else
13505             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13506
13507             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13508
13509             /* [HGM] PV time: now locate end of PV info */
13510             while( *++sep >= '0' && *sep <= '9'); // strip depth
13511             if(time >= 0)
13512             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13513             if(sec >= 0)
13514             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13515             if(deci >= 0)
13516             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13517             while(*sep == ' ') sep++;
13518         }
13519
13520         if( depth <= 0 ) {
13521             return text;
13522         }
13523
13524         if( time < 0 ) {
13525             time = -1;
13526         }
13527
13528         pvInfoList[index-1].depth = depth;
13529         pvInfoList[index-1].score = score;
13530         pvInfoList[index-1].time  = 10*time; // centi-sec
13531         if(*sep == '}') *sep = 0; else *--sep = '{';
13532         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13533     }
13534     return sep;
13535 }
13536
13537 void
13538 SendToProgram(message, cps)
13539      char *message;
13540      ChessProgramState *cps;
13541 {
13542     int count, outCount, error;
13543     char buf[MSG_SIZ];
13544
13545     if (cps->pr == NULL) return;
13546     Attention(cps);
13547
13548     if (appData.debugMode) {
13549         TimeMark now;
13550         GetTimeMark(&now);
13551         fprintf(debugFP, "%ld >%-6s: %s",
13552                 SubtractTimeMarks(&now, &programStartTime),
13553                 cps->which, message);
13554     }
13555
13556     count = strlen(message);
13557     outCount = OutputToProcess(cps->pr, message, count, &error);
13558     if (outCount < count && !exiting
13559                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13560       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13561         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13562             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13563                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13564                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13565             } else {
13566                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13567             }
13568             gameInfo.resultDetails = StrSave(buf);
13569         }
13570         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13571     }
13572 }
13573
13574 void
13575 ReceiveFromProgram(isr, closure, message, count, error)
13576      InputSourceRef isr;
13577      VOIDSTAR closure;
13578      char *message;
13579      int count;
13580      int error;
13581 {
13582     char *end_str;
13583     char buf[MSG_SIZ];
13584     ChessProgramState *cps = (ChessProgramState *)closure;
13585
13586     if (isr != cps->isr) return; /* Killed intentionally */
13587     if (count <= 0) {
13588         if (count == 0) {
13589             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13590                     cps->which, cps->program);
13591         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13592                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13593                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13594                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13595                 } else {
13596                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13597                 }
13598                 gameInfo.resultDetails = StrSave(buf);
13599             }
13600             RemoveInputSource(cps->isr);
13601             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13602         } else {
13603             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13604                     cps->which, cps->program);
13605             RemoveInputSource(cps->isr);
13606
13607             /* [AS] Program is misbehaving badly... kill it */
13608             if( count == -2 ) {
13609                 DestroyChildProcess( cps->pr, 9 );
13610                 cps->pr = NoProc;
13611             }
13612
13613             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13614         }
13615         return;
13616     }
13617
13618     if ((end_str = strchr(message, '\r')) != NULL)
13619       *end_str = NULLCHAR;
13620     if ((end_str = strchr(message, '\n')) != NULL)
13621       *end_str = NULLCHAR;
13622
13623     if (appData.debugMode) {
13624         TimeMark now; int print = 1;
13625         char *quote = ""; char c; int i;
13626
13627         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13628                 char start = message[0];
13629                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13630                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13631                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13632                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13633                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13634                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13635                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13636                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13637                    sscanf(message, "hint: %c", &c)!=1 && 
13638                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13639                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13640                     print = (appData.engineComments >= 2);
13641                 }
13642                 message[0] = start; // restore original message
13643         }
13644         if(print) {
13645                 GetTimeMark(&now);
13646                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13647                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13648                         quote,
13649                         message);
13650         }
13651     }
13652
13653     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13654     if (appData.icsEngineAnalyze) {
13655         if (strstr(message, "whisper") != NULL ||
13656              strstr(message, "kibitz") != NULL ||
13657             strstr(message, "tellics") != NULL) return;
13658     }
13659
13660     HandleMachineMove(message, cps);
13661 }
13662
13663
13664 void
13665 SendTimeControl(cps, mps, tc, inc, sd, st)
13666      ChessProgramState *cps;
13667      int mps, inc, sd, st;
13668      long tc;
13669 {
13670     char buf[MSG_SIZ];
13671     int seconds;
13672
13673     if( timeControl_2 > 0 ) {
13674         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13675             tc = timeControl_2;
13676         }
13677     }
13678     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13679     inc /= cps->timeOdds;
13680     st  /= cps->timeOdds;
13681
13682     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13683
13684     if (st > 0) {
13685       /* Set exact time per move, normally using st command */
13686       if (cps->stKludge) {
13687         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13688         seconds = st % 60;
13689         if (seconds == 0) {
13690           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13691         } else {
13692           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13693         }
13694       } else {
13695         snprintf(buf, MSG_SIZ, "st %d\n", st);
13696       }
13697     } else {
13698       /* Set conventional or incremental time control, using level command */
13699       if (seconds == 0) {
13700         /* Note old gnuchess bug -- minutes:seconds used to not work.
13701            Fixed in later versions, but still avoid :seconds
13702            when seconds is 0. */
13703         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13704       } else {
13705         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13706                  seconds, inc/1000.);
13707       }
13708     }
13709     SendToProgram(buf, cps);
13710
13711     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13712     /* Orthogonally, limit search to given depth */
13713     if (sd > 0) {
13714       if (cps->sdKludge) {
13715         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13716       } else {
13717         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13718       }
13719       SendToProgram(buf, cps);
13720     }
13721
13722     if(cps->nps > 0) { /* [HGM] nps */
13723         if(cps->supportsNPS == FALSE)
13724           cps->nps = -1; // don't use if engine explicitly says not supported!
13725         else {
13726           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13727           SendToProgram(buf, cps);
13728         }
13729     }
13730 }
13731
13732 ChessProgramState *WhitePlayer()
13733 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13734 {
13735     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13736        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13737         return &second;
13738     return &first;
13739 }
13740
13741 void
13742 SendTimeRemaining(cps, machineWhite)
13743      ChessProgramState *cps;
13744      int /*boolean*/ machineWhite;
13745 {
13746     char message[MSG_SIZ];
13747     long time, otime;
13748
13749     /* Note: this routine must be called when the clocks are stopped
13750        or when they have *just* been set or switched; otherwise
13751        it will be off by the time since the current tick started.
13752     */
13753     if (machineWhite) {
13754         time = whiteTimeRemaining / 10;
13755         otime = blackTimeRemaining / 10;
13756     } else {
13757         time = blackTimeRemaining / 10;
13758         otime = whiteTimeRemaining / 10;
13759     }
13760     /* [HGM] translate opponent's time by time-odds factor */
13761     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13762     if (appData.debugMode) {
13763         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13764     }
13765
13766     if (time <= 0) time = 1;
13767     if (otime <= 0) otime = 1;
13768
13769     snprintf(message, MSG_SIZ, "time %ld\n", time);
13770     SendToProgram(message, cps);
13771
13772     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13773     SendToProgram(message, cps);
13774 }
13775
13776 int
13777 BoolFeature(p, name, loc, cps)
13778      char **p;
13779      char *name;
13780      int *loc;
13781      ChessProgramState *cps;
13782 {
13783   char buf[MSG_SIZ];
13784   int len = strlen(name);
13785   int val;
13786
13787   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13788     (*p) += len + 1;
13789     sscanf(*p, "%d", &val);
13790     *loc = (val != 0);
13791     while (**p && **p != ' ')
13792       (*p)++;
13793     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13794     SendToProgram(buf, cps);
13795     return TRUE;
13796   }
13797   return FALSE;
13798 }
13799
13800 int
13801 IntFeature(p, name, loc, cps)
13802      char **p;
13803      char *name;
13804      int *loc;
13805      ChessProgramState *cps;
13806 {
13807   char buf[MSG_SIZ];
13808   int len = strlen(name);
13809   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13810     (*p) += len + 1;
13811     sscanf(*p, "%d", loc);
13812     while (**p && **p != ' ') (*p)++;
13813     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13814     SendToProgram(buf, cps);
13815     return TRUE;
13816   }
13817   return FALSE;
13818 }
13819
13820 int
13821 StringFeature(p, name, loc, cps)
13822      char **p;
13823      char *name;
13824      char loc[];
13825      ChessProgramState *cps;
13826 {
13827   char buf[MSG_SIZ];
13828   int len = strlen(name);
13829   if (strncmp((*p), name, len) == 0
13830       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13831     (*p) += len + 2;
13832     sscanf(*p, "%[^\"]", loc);
13833     while (**p && **p != '\"') (*p)++;
13834     if (**p == '\"') (*p)++;
13835     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13836     SendToProgram(buf, cps);
13837     return TRUE;
13838   }
13839   return FALSE;
13840 }
13841
13842 int
13843 ParseOption(Option *opt, ChessProgramState *cps)
13844 // [HGM] options: process the string that defines an engine option, and determine
13845 // name, type, default value, and allowed value range
13846 {
13847         char *p, *q, buf[MSG_SIZ];
13848         int n, min = (-1)<<31, max = 1<<31, def;
13849
13850         if(p = strstr(opt->name, " -spin ")) {
13851             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13852             if(max < min) max = min; // enforce consistency
13853             if(def < min) def = min;
13854             if(def > max) def = max;
13855             opt->value = def;
13856             opt->min = min;
13857             opt->max = max;
13858             opt->type = Spin;
13859         } else if((p = strstr(opt->name, " -slider "))) {
13860             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13861             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13862             if(max < min) max = min; // enforce consistency
13863             if(def < min) def = min;
13864             if(def > max) def = max;
13865             opt->value = def;
13866             opt->min = min;
13867             opt->max = max;
13868             opt->type = Spin; // Slider;
13869         } else if((p = strstr(opt->name, " -string "))) {
13870             opt->textValue = p+9;
13871             opt->type = TextBox;
13872         } else if((p = strstr(opt->name, " -file "))) {
13873             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13874             opt->textValue = p+7;
13875             opt->type = TextBox; // FileName;
13876         } else if((p = strstr(opt->name, " -path "))) {
13877             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13878             opt->textValue = p+7;
13879             opt->type = TextBox; // PathName;
13880         } else if(p = strstr(opt->name, " -check ")) {
13881             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13882             opt->value = (def != 0);
13883             opt->type = CheckBox;
13884         } else if(p = strstr(opt->name, " -combo ")) {
13885             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13886             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13887             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13888             opt->value = n = 0;
13889             while(q = StrStr(q, " /// ")) {
13890                 n++; *q = 0;    // count choices, and null-terminate each of them
13891                 q += 5;
13892                 if(*q == '*') { // remember default, which is marked with * prefix
13893                     q++;
13894                     opt->value = n;
13895                 }
13896                 cps->comboList[cps->comboCnt++] = q;
13897             }
13898             cps->comboList[cps->comboCnt++] = NULL;
13899             opt->max = n + 1;
13900             opt->type = ComboBox;
13901         } else if(p = strstr(opt->name, " -button")) {
13902             opt->type = Button;
13903         } else if(p = strstr(opt->name, " -save")) {
13904             opt->type = SaveButton;
13905         } else return FALSE;
13906         *p = 0; // terminate option name
13907         // now look if the command-line options define a setting for this engine option.
13908         if(cps->optionSettings && cps->optionSettings[0])
13909             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13910         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13911           snprintf(buf, MSG_SIZ, "option %s", p);
13912                 if(p = strstr(buf, ",")) *p = 0;
13913                 if(q = strchr(buf, '=')) switch(opt->type) {
13914                     case ComboBox:
13915                         for(n=0; n<opt->max; n++)
13916                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13917                         break;
13918                     case TextBox:
13919                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13920                         break;
13921                     case Spin:
13922                     case CheckBox:
13923                         opt->value = atoi(q+1);
13924                     default:
13925                         break;
13926                 }
13927                 strcat(buf, "\n");
13928                 SendToProgram(buf, cps);
13929         }
13930         return TRUE;
13931 }
13932
13933 void
13934 FeatureDone(cps, val)
13935      ChessProgramState* cps;
13936      int val;
13937 {
13938   DelayedEventCallback cb = GetDelayedEvent();
13939   if ((cb == InitBackEnd3 && cps == &first) ||
13940       (cb == SettingsMenuIfReady && cps == &second) ||
13941       (cb == TwoMachinesEventIfReady && cps == &second)) {
13942     CancelDelayedEvent();
13943     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13944   }
13945   cps->initDone = val;
13946 }
13947
13948 /* Parse feature command from engine */
13949 void
13950 ParseFeatures(args, cps)
13951      char* args;
13952      ChessProgramState *cps;
13953 {
13954   char *p = args;
13955   char *q;
13956   int val;
13957   char buf[MSG_SIZ];
13958
13959   for (;;) {
13960     while (*p == ' ') p++;
13961     if (*p == NULLCHAR) return;
13962
13963     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13964     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13965     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13966     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13967     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13968     if (BoolFeature(&p, "reuse", &val, cps)) {
13969       /* Engine can disable reuse, but can't enable it if user said no */
13970       if (!val) cps->reuse = FALSE;
13971       continue;
13972     }
13973     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13974     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13975       if (gameMode == TwoMachinesPlay) {
13976         DisplayTwoMachinesTitle();
13977       } else {
13978         DisplayTitle("");
13979       }
13980       continue;
13981     }
13982     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13983     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13984     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13985     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13986     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13987     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13988     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13989     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13990     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13991     if (IntFeature(&p, "done", &val, cps)) {
13992       FeatureDone(cps, val);
13993       continue;
13994     }
13995     /* Added by Tord: */
13996     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13997     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13998     /* End of additions by Tord */
13999
14000     /* [HGM] added features: */
14001     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14002     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14003     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14004     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14005     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14006     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14007     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14008         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14009           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14010             SendToProgram(buf, cps);
14011             continue;
14012         }
14013         if(cps->nrOptions >= MAX_OPTIONS) {
14014             cps->nrOptions--;
14015             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
14016             DisplayError(buf, 0);
14017         }
14018         continue;
14019     }
14020     /* End of additions by HGM */
14021
14022     /* unknown feature: complain and skip */
14023     q = p;
14024     while (*q && *q != '=') q++;
14025     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14026     SendToProgram(buf, cps);
14027     p = q;
14028     if (*p == '=') {
14029       p++;
14030       if (*p == '\"') {
14031         p++;
14032         while (*p && *p != '\"') p++;
14033         if (*p == '\"') p++;
14034       } else {
14035         while (*p && *p != ' ') p++;
14036       }
14037     }
14038   }
14039
14040 }
14041
14042 void
14043 PeriodicUpdatesEvent(newState)
14044      int newState;
14045 {
14046     if (newState == appData.periodicUpdates)
14047       return;
14048
14049     appData.periodicUpdates=newState;
14050
14051     /* Display type changes, so update it now */
14052 //    DisplayAnalysis();
14053
14054     /* Get the ball rolling again... */
14055     if (newState) {
14056         AnalysisPeriodicEvent(1);
14057         StartAnalysisClock();
14058     }
14059 }
14060
14061 void
14062 PonderNextMoveEvent(newState)
14063      int newState;
14064 {
14065     if (newState == appData.ponderNextMove) return;
14066     if (gameMode == EditPosition) EditPositionDone(TRUE);
14067     if (newState) {
14068         SendToProgram("hard\n", &first);
14069         if (gameMode == TwoMachinesPlay) {
14070             SendToProgram("hard\n", &second);
14071         }
14072     } else {
14073         SendToProgram("easy\n", &first);
14074         thinkOutput[0] = NULLCHAR;
14075         if (gameMode == TwoMachinesPlay) {
14076             SendToProgram("easy\n", &second);
14077         }
14078     }
14079     appData.ponderNextMove = newState;
14080 }
14081
14082 void
14083 NewSettingEvent(option, feature, command, value)
14084      char *command;
14085      int option, value, *feature;
14086 {
14087     char buf[MSG_SIZ];
14088
14089     if (gameMode == EditPosition) EditPositionDone(TRUE);
14090     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14091     if(feature == NULL || *feature) SendToProgram(buf, &first);
14092     if (gameMode == TwoMachinesPlay) {
14093         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14094     }
14095 }
14096
14097 void
14098 ShowThinkingEvent()
14099 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14100 {
14101     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14102     int newState = appData.showThinking
14103         // [HGM] thinking: other features now need thinking output as well
14104         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14105
14106     if (oldState == newState) return;
14107     oldState = newState;
14108     if (gameMode == EditPosition) EditPositionDone(TRUE);
14109     if (oldState) {
14110         SendToProgram("post\n", &first);
14111         if (gameMode == TwoMachinesPlay) {
14112             SendToProgram("post\n", &second);
14113         }
14114     } else {
14115         SendToProgram("nopost\n", &first);
14116         thinkOutput[0] = NULLCHAR;
14117         if (gameMode == TwoMachinesPlay) {
14118             SendToProgram("nopost\n", &second);
14119         }
14120     }
14121 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14122 }
14123
14124 void
14125 AskQuestionEvent(title, question, replyPrefix, which)
14126      char *title; char *question; char *replyPrefix; char *which;
14127 {
14128   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14129   if (pr == NoProc) return;
14130   AskQuestion(title, question, replyPrefix, pr);
14131 }
14132
14133 void
14134 DisplayMove(moveNumber)
14135      int moveNumber;
14136 {
14137     char message[MSG_SIZ];
14138     char res[MSG_SIZ];
14139     char cpThinkOutput[MSG_SIZ];
14140
14141     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14142
14143     if (moveNumber == forwardMostMove - 1 ||
14144         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14145
14146         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14147
14148         if (strchr(cpThinkOutput, '\n')) {
14149             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14150         }
14151     } else {
14152         *cpThinkOutput = NULLCHAR;
14153     }
14154
14155     /* [AS] Hide thinking from human user */
14156     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14157         *cpThinkOutput = NULLCHAR;
14158         if( thinkOutput[0] != NULLCHAR ) {
14159             int i;
14160
14161             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14162                 cpThinkOutput[i] = '.';
14163             }
14164             cpThinkOutput[i] = NULLCHAR;
14165             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14166         }
14167     }
14168
14169     if (moveNumber == forwardMostMove - 1 &&
14170         gameInfo.resultDetails != NULL) {
14171         if (gameInfo.resultDetails[0] == NULLCHAR) {
14172           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14173         } else {
14174           snprintf(res, MSG_SIZ, " {%s} %s",
14175                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14176         }
14177     } else {
14178         res[0] = NULLCHAR;
14179     }
14180
14181     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14182         DisplayMessage(res, cpThinkOutput);
14183     } else {
14184       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14185                 WhiteOnMove(moveNumber) ? " " : ".. ",
14186                 parseList[moveNumber], res);
14187         DisplayMessage(message, cpThinkOutput);
14188     }
14189 }
14190
14191 void
14192 DisplayComment(moveNumber, text)
14193      int moveNumber;
14194      char *text;
14195 {
14196     char title[MSG_SIZ];
14197     char buf[8000]; // comment can be long!
14198     int score, depth;
14199
14200     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14201       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14202     } else {
14203       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14204               WhiteOnMove(moveNumber) ? " " : ".. ",
14205               parseList[moveNumber]);
14206     }
14207     // [HGM] PV info: display PV info together with (or as) comment
14208     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14209       if(text == NULL) text = "";
14210       score = pvInfoList[moveNumber].score;
14211       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14212               depth, (pvInfoList[moveNumber].time+50)/100, text);
14213       text = buf;
14214     }
14215     if (text != NULL && (appData.autoDisplayComment || commentUp))
14216         CommentPopUp(title, text);
14217 }
14218
14219 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14220  * might be busy thinking or pondering.  It can be omitted if your
14221  * gnuchess is configured to stop thinking immediately on any user
14222  * input.  However, that gnuchess feature depends on the FIONREAD
14223  * ioctl, which does not work properly on some flavors of Unix.
14224  */
14225 void
14226 Attention(cps)
14227      ChessProgramState *cps;
14228 {
14229 #if ATTENTION
14230     if (!cps->useSigint) return;
14231     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14232     switch (gameMode) {
14233       case MachinePlaysWhite:
14234       case MachinePlaysBlack:
14235       case TwoMachinesPlay:
14236       case IcsPlayingWhite:
14237       case IcsPlayingBlack:
14238       case AnalyzeMode:
14239       case AnalyzeFile:
14240         /* Skip if we know it isn't thinking */
14241         if (!cps->maybeThinking) return;
14242         if (appData.debugMode)
14243           fprintf(debugFP, "Interrupting %s\n", cps->which);
14244         InterruptChildProcess(cps->pr);
14245         cps->maybeThinking = FALSE;
14246         break;
14247       default:
14248         break;
14249     }
14250 #endif /*ATTENTION*/
14251 }
14252
14253 int
14254 CheckFlags()
14255 {
14256     if (whiteTimeRemaining <= 0) {
14257         if (!whiteFlag) {
14258             whiteFlag = TRUE;
14259             if (appData.icsActive) {
14260                 if (appData.autoCallFlag &&
14261                     gameMode == IcsPlayingBlack && !blackFlag) {
14262                   SendToICS(ics_prefix);
14263                   SendToICS("flag\n");
14264                 }
14265             } else {
14266                 if (blackFlag) {
14267                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14268                 } else {
14269                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14270                     if (appData.autoCallFlag) {
14271                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14272                         return TRUE;
14273                     }
14274                 }
14275             }
14276         }
14277     }
14278     if (blackTimeRemaining <= 0) {
14279         if (!blackFlag) {
14280             blackFlag = TRUE;
14281             if (appData.icsActive) {
14282                 if (appData.autoCallFlag &&
14283                     gameMode == IcsPlayingWhite && !whiteFlag) {
14284                   SendToICS(ics_prefix);
14285                   SendToICS("flag\n");
14286                 }
14287             } else {
14288                 if (whiteFlag) {
14289                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14290                 } else {
14291                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14292                     if (appData.autoCallFlag) {
14293                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14294                         return TRUE;
14295                     }
14296                 }
14297             }
14298         }
14299     }
14300     return FALSE;
14301 }
14302
14303 void
14304 CheckTimeControl()
14305 {
14306     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14307         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14308
14309     /*
14310      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14311      */
14312     if ( !WhiteOnMove(forwardMostMove) ) {
14313         /* White made time control */
14314         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14315         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14316         /* [HGM] time odds: correct new time quota for time odds! */
14317                                             / WhitePlayer()->timeOdds;
14318         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14319     } else {
14320         lastBlack -= blackTimeRemaining;
14321         /* Black made time control */
14322         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14323                                             / WhitePlayer()->other->timeOdds;
14324         lastWhite = whiteTimeRemaining;
14325     }
14326 }
14327
14328 void
14329 DisplayBothClocks()
14330 {
14331     int wom = gameMode == EditPosition ?
14332       !blackPlaysFirst : WhiteOnMove(currentMove);
14333     DisplayWhiteClock(whiteTimeRemaining, wom);
14334     DisplayBlackClock(blackTimeRemaining, !wom);
14335 }
14336
14337
14338 /* Timekeeping seems to be a portability nightmare.  I think everyone
14339    has ftime(), but I'm really not sure, so I'm including some ifdefs
14340    to use other calls if you don't.  Clocks will be less accurate if
14341    you have neither ftime nor gettimeofday.
14342 */
14343
14344 /* VS 2008 requires the #include outside of the function */
14345 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14346 #include <sys/timeb.h>
14347 #endif
14348
14349 /* Get the current time as a TimeMark */
14350 void
14351 GetTimeMark(tm)
14352      TimeMark *tm;
14353 {
14354 #if HAVE_GETTIMEOFDAY
14355
14356     struct timeval timeVal;
14357     struct timezone timeZone;
14358
14359     gettimeofday(&timeVal, &timeZone);
14360     tm->sec = (long) timeVal.tv_sec;
14361     tm->ms = (int) (timeVal.tv_usec / 1000L);
14362
14363 #else /*!HAVE_GETTIMEOFDAY*/
14364 #if HAVE_FTIME
14365
14366 // include <sys/timeb.h> / moved to just above start of function
14367     struct timeb timeB;
14368
14369     ftime(&timeB);
14370     tm->sec = (long) timeB.time;
14371     tm->ms = (int) timeB.millitm;
14372
14373 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14374     tm->sec = (long) time(NULL);
14375     tm->ms = 0;
14376 #endif
14377 #endif
14378 }
14379
14380 /* Return the difference in milliseconds between two
14381    time marks.  We assume the difference will fit in a long!
14382 */
14383 long
14384 SubtractTimeMarks(tm2, tm1)
14385      TimeMark *tm2, *tm1;
14386 {
14387     return 1000L*(tm2->sec - tm1->sec) +
14388            (long) (tm2->ms - tm1->ms);
14389 }
14390
14391
14392 /*
14393  * Code to manage the game clocks.
14394  *
14395  * In tournament play, black starts the clock and then white makes a move.
14396  * We give the human user a slight advantage if he is playing white---the
14397  * clocks don't run until he makes his first move, so it takes zero time.
14398  * Also, we don't account for network lag, so we could get out of sync
14399  * with GNU Chess's clock -- but then, referees are always right.
14400  */
14401
14402 static TimeMark tickStartTM;
14403 static long intendedTickLength;
14404
14405 long
14406 NextTickLength(timeRemaining)
14407      long timeRemaining;
14408 {
14409     long nominalTickLength, nextTickLength;
14410
14411     if (timeRemaining > 0L && timeRemaining <= 10000L)
14412       nominalTickLength = 100L;
14413     else
14414       nominalTickLength = 1000L;
14415     nextTickLength = timeRemaining % nominalTickLength;
14416     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14417
14418     return nextTickLength;
14419 }
14420
14421 /* Adjust clock one minute up or down */
14422 void
14423 AdjustClock(Boolean which, int dir)
14424 {
14425     if(which) blackTimeRemaining += 60000*dir;
14426     else      whiteTimeRemaining += 60000*dir;
14427     DisplayBothClocks();
14428 }
14429
14430 /* Stop clocks and reset to a fresh time control */
14431 void
14432 ResetClocks()
14433 {
14434     (void) StopClockTimer();
14435     if (appData.icsActive) {
14436         whiteTimeRemaining = blackTimeRemaining = 0;
14437     } else if (searchTime) {
14438         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14439         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14440     } else { /* [HGM] correct new time quote for time odds */
14441         whiteTC = blackTC = fullTimeControlString;
14442         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14443         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14444     }
14445     if (whiteFlag || blackFlag) {
14446         DisplayTitle("");
14447         whiteFlag = blackFlag = FALSE;
14448     }
14449     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14450     DisplayBothClocks();
14451 }
14452
14453 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14454
14455 /* Decrement running clock by amount of time that has passed */
14456 void
14457 DecrementClocks()
14458 {
14459     long timeRemaining;
14460     long lastTickLength, fudge;
14461     TimeMark now;
14462
14463     if (!appData.clockMode) return;
14464     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14465
14466     GetTimeMark(&now);
14467
14468     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14469
14470     /* Fudge if we woke up a little too soon */
14471     fudge = intendedTickLength - lastTickLength;
14472     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14473
14474     if (WhiteOnMove(forwardMostMove)) {
14475         if(whiteNPS >= 0) lastTickLength = 0;
14476         timeRemaining = whiteTimeRemaining -= lastTickLength;
14477         if(timeRemaining < 0 && !appData.icsActive) {
14478             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14479             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14480                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14481                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14482             }
14483         }
14484         DisplayWhiteClock(whiteTimeRemaining - fudge,
14485                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14486     } else {
14487         if(blackNPS >= 0) lastTickLength = 0;
14488         timeRemaining = blackTimeRemaining -= lastTickLength;
14489         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14490             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14491             if(suddenDeath) {
14492                 blackStartMove = forwardMostMove;
14493                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14494             }
14495         }
14496         DisplayBlackClock(blackTimeRemaining - fudge,
14497                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14498     }
14499     if (CheckFlags()) return;
14500
14501     tickStartTM = now;
14502     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14503     StartClockTimer(intendedTickLength);
14504
14505     /* if the time remaining has fallen below the alarm threshold, sound the
14506      * alarm. if the alarm has sounded and (due to a takeback or time control
14507      * with increment) the time remaining has increased to a level above the
14508      * threshold, reset the alarm so it can sound again.
14509      */
14510
14511     if (appData.icsActive && appData.icsAlarm) {
14512
14513         /* make sure we are dealing with the user's clock */
14514         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14515                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14516            )) return;
14517
14518         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14519             alarmSounded = FALSE;
14520         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14521             PlayAlarmSound();
14522             alarmSounded = TRUE;
14523         }
14524     }
14525 }
14526
14527
14528 /* A player has just moved, so stop the previously running
14529    clock and (if in clock mode) start the other one.
14530    We redisplay both clocks in case we're in ICS mode, because
14531    ICS gives us an update to both clocks after every move.
14532    Note that this routine is called *after* forwardMostMove
14533    is updated, so the last fractional tick must be subtracted
14534    from the color that is *not* on move now.
14535 */
14536 void
14537 SwitchClocks(int newMoveNr)
14538 {
14539     long lastTickLength;
14540     TimeMark now;
14541     int flagged = FALSE;
14542
14543     GetTimeMark(&now);
14544
14545     if (StopClockTimer() && appData.clockMode) {
14546         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14547         if (!WhiteOnMove(forwardMostMove)) {
14548             if(blackNPS >= 0) lastTickLength = 0;
14549             blackTimeRemaining -= lastTickLength;
14550            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14551 //         if(pvInfoList[forwardMostMove].time == -1)
14552                  pvInfoList[forwardMostMove].time =               // use GUI time
14553                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14554         } else {
14555            if(whiteNPS >= 0) lastTickLength = 0;
14556            whiteTimeRemaining -= lastTickLength;
14557            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14558 //         if(pvInfoList[forwardMostMove].time == -1)
14559                  pvInfoList[forwardMostMove].time =
14560                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14561         }
14562         flagged = CheckFlags();
14563     }
14564     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14565     CheckTimeControl();
14566
14567     if (flagged || !appData.clockMode) return;
14568
14569     switch (gameMode) {
14570       case MachinePlaysBlack:
14571       case MachinePlaysWhite:
14572       case BeginningOfGame:
14573         if (pausing) return;
14574         break;
14575
14576       case EditGame:
14577       case PlayFromGameFile:
14578       case IcsExamining:
14579         return;
14580
14581       default:
14582         break;
14583     }
14584
14585     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14586         if(WhiteOnMove(forwardMostMove))
14587              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14588         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14589     }
14590
14591     tickStartTM = now;
14592     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14593       whiteTimeRemaining : blackTimeRemaining);
14594     StartClockTimer(intendedTickLength);
14595 }
14596
14597
14598 /* Stop both clocks */
14599 void
14600 StopClocks()
14601 {
14602     long lastTickLength;
14603     TimeMark now;
14604
14605     if (!StopClockTimer()) return;
14606     if (!appData.clockMode) return;
14607
14608     GetTimeMark(&now);
14609
14610     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14611     if (WhiteOnMove(forwardMostMove)) {
14612         if(whiteNPS >= 0) lastTickLength = 0;
14613         whiteTimeRemaining -= lastTickLength;
14614         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14615     } else {
14616         if(blackNPS >= 0) lastTickLength = 0;
14617         blackTimeRemaining -= lastTickLength;
14618         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14619     }
14620     CheckFlags();
14621 }
14622
14623 /* Start clock of player on move.  Time may have been reset, so
14624    if clock is already running, stop and restart it. */
14625 void
14626 StartClocks()
14627 {
14628     (void) StopClockTimer(); /* in case it was running already */
14629     DisplayBothClocks();
14630     if (CheckFlags()) return;
14631
14632     if (!appData.clockMode) return;
14633     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14634
14635     GetTimeMark(&tickStartTM);
14636     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14637       whiteTimeRemaining : blackTimeRemaining);
14638
14639    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14640     whiteNPS = blackNPS = -1;
14641     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14642        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14643         whiteNPS = first.nps;
14644     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14645        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14646         blackNPS = first.nps;
14647     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14648         whiteNPS = second.nps;
14649     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14650         blackNPS = second.nps;
14651     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14652
14653     StartClockTimer(intendedTickLength);
14654 }
14655
14656 char *
14657 TimeString(ms)
14658      long ms;
14659 {
14660     long second, minute, hour, day;
14661     char *sign = "";
14662     static char buf[32];
14663
14664     if (ms > 0 && ms <= 9900) {
14665       /* convert milliseconds to tenths, rounding up */
14666       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14667
14668       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14669       return buf;
14670     }
14671
14672     /* convert milliseconds to seconds, rounding up */
14673     /* use floating point to avoid strangeness of integer division
14674        with negative dividends on many machines */
14675     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14676
14677     if (second < 0) {
14678         sign = "-";
14679         second = -second;
14680     }
14681
14682     day = second / (60 * 60 * 24);
14683     second = second % (60 * 60 * 24);
14684     hour = second / (60 * 60);
14685     second = second % (60 * 60);
14686     minute = second / 60;
14687     second = second % 60;
14688
14689     if (day > 0)
14690       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14691               sign, day, hour, minute, second);
14692     else if (hour > 0)
14693       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14694     else
14695       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14696
14697     return buf;
14698 }
14699
14700
14701 /*
14702  * This is necessary because some C libraries aren't ANSI C compliant yet.
14703  */
14704 char *
14705 StrStr(string, match)
14706      char *string, *match;
14707 {
14708     int i, length;
14709
14710     length = strlen(match);
14711
14712     for (i = strlen(string) - length; i >= 0; i--, string++)
14713       if (!strncmp(match, string, length))
14714         return string;
14715
14716     return NULL;
14717 }
14718
14719 char *
14720 StrCaseStr(string, match)
14721      char *string, *match;
14722 {
14723     int i, j, length;
14724
14725     length = strlen(match);
14726
14727     for (i = strlen(string) - length; i >= 0; i--, string++) {
14728         for (j = 0; j < length; j++) {
14729             if (ToLower(match[j]) != ToLower(string[j]))
14730               break;
14731         }
14732         if (j == length) return string;
14733     }
14734
14735     return NULL;
14736 }
14737
14738 #ifndef _amigados
14739 int
14740 StrCaseCmp(s1, s2)
14741      char *s1, *s2;
14742 {
14743     char c1, c2;
14744
14745     for (;;) {
14746         c1 = ToLower(*s1++);
14747         c2 = ToLower(*s2++);
14748         if (c1 > c2) return 1;
14749         if (c1 < c2) return -1;
14750         if (c1 == NULLCHAR) return 0;
14751     }
14752 }
14753
14754
14755 int
14756 ToLower(c)
14757      int c;
14758 {
14759     return isupper(c) ? tolower(c) : c;
14760 }
14761
14762
14763 int
14764 ToUpper(c)
14765      int c;
14766 {
14767     return islower(c) ? toupper(c) : c;
14768 }
14769 #endif /* !_amigados    */
14770
14771 char *
14772 StrSave(s)
14773      char *s;
14774 {
14775   char *ret;
14776
14777   if ((ret = (char *) malloc(strlen(s) + 1)))
14778     {
14779       safeStrCpy(ret, s, strlen(s)+1);
14780     }
14781   return ret;
14782 }
14783
14784 char *
14785 StrSavePtr(s, savePtr)
14786      char *s, **savePtr;
14787 {
14788     if (*savePtr) {
14789         free(*savePtr);
14790     }
14791     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14792       safeStrCpy(*savePtr, s, strlen(s)+1);
14793     }
14794     return(*savePtr);
14795 }
14796
14797 char *
14798 PGNDate()
14799 {
14800     time_t clock;
14801     struct tm *tm;
14802     char buf[MSG_SIZ];
14803
14804     clock = time((time_t *)NULL);
14805     tm = localtime(&clock);
14806     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14807             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14808     return StrSave(buf);
14809 }
14810
14811
14812 char *
14813 PositionToFEN(move, overrideCastling)
14814      int move;
14815      char *overrideCastling;
14816 {
14817     int i, j, fromX, fromY, toX, toY;
14818     int whiteToPlay;
14819     char buf[128];
14820     char *p, *q;
14821     int emptycount;
14822     ChessSquare piece;
14823
14824     whiteToPlay = (gameMode == EditPosition) ?
14825       !blackPlaysFirst : (move % 2 == 0);
14826     p = buf;
14827
14828     /* Piece placement data */
14829     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14830         emptycount = 0;
14831         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14832             if (boards[move][i][j] == EmptySquare) {
14833                 emptycount++;
14834             } else { ChessSquare piece = boards[move][i][j];
14835                 if (emptycount > 0) {
14836                     if(emptycount<10) /* [HGM] can be >= 10 */
14837                         *p++ = '0' + emptycount;
14838                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14839                     emptycount = 0;
14840                 }
14841                 if(PieceToChar(piece) == '+') {
14842                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14843                     *p++ = '+';
14844                     piece = (ChessSquare)(DEMOTED piece);
14845                 }
14846                 *p++ = PieceToChar(piece);
14847                 if(p[-1] == '~') {
14848                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14849                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14850                     *p++ = '~';
14851                 }
14852             }
14853         }
14854         if (emptycount > 0) {
14855             if(emptycount<10) /* [HGM] can be >= 10 */
14856                 *p++ = '0' + emptycount;
14857             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14858             emptycount = 0;
14859         }
14860         *p++ = '/';
14861     }
14862     *(p - 1) = ' ';
14863
14864     /* [HGM] print Crazyhouse or Shogi holdings */
14865     if( gameInfo.holdingsWidth ) {
14866         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14867         q = p;
14868         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14869             piece = boards[move][i][BOARD_WIDTH-1];
14870             if( piece != EmptySquare )
14871               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14872                   *p++ = PieceToChar(piece);
14873         }
14874         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14875             piece = boards[move][BOARD_HEIGHT-i-1][0];
14876             if( piece != EmptySquare )
14877               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14878                   *p++ = PieceToChar(piece);
14879         }
14880
14881         if( q == p ) *p++ = '-';
14882         *p++ = ']';
14883         *p++ = ' ';
14884     }
14885
14886     /* Active color */
14887     *p++ = whiteToPlay ? 'w' : 'b';
14888     *p++ = ' ';
14889
14890   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14891     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14892   } else {
14893   if(nrCastlingRights) {
14894      q = p;
14895      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14896        /* [HGM] write directly from rights */
14897            if(boards[move][CASTLING][2] != NoRights &&
14898               boards[move][CASTLING][0] != NoRights   )
14899                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14900            if(boards[move][CASTLING][2] != NoRights &&
14901               boards[move][CASTLING][1] != NoRights   )
14902                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14903            if(boards[move][CASTLING][5] != NoRights &&
14904               boards[move][CASTLING][3] != NoRights   )
14905                 *p++ = boards[move][CASTLING][3] + AAA;
14906            if(boards[move][CASTLING][5] != NoRights &&
14907               boards[move][CASTLING][4] != NoRights   )
14908                 *p++ = boards[move][CASTLING][4] + AAA;
14909      } else {
14910
14911         /* [HGM] write true castling rights */
14912         if( nrCastlingRights == 6 ) {
14913             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14914                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14915             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14916                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14917             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14918                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14919             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14920                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14921         }
14922      }
14923      if (q == p) *p++ = '-'; /* No castling rights */
14924      *p++ = ' ';
14925   }
14926
14927   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14928      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14929     /* En passant target square */
14930     if (move > backwardMostMove) {
14931         fromX = moveList[move - 1][0] - AAA;
14932         fromY = moveList[move - 1][1] - ONE;
14933         toX = moveList[move - 1][2] - AAA;
14934         toY = moveList[move - 1][3] - ONE;
14935         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14936             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14937             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14938             fromX == toX) {
14939             /* 2-square pawn move just happened */
14940             *p++ = toX + AAA;
14941             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14942         } else {
14943             *p++ = '-';
14944         }
14945     } else if(move == backwardMostMove) {
14946         // [HGM] perhaps we should always do it like this, and forget the above?
14947         if((signed char)boards[move][EP_STATUS] >= 0) {
14948             *p++ = boards[move][EP_STATUS] + AAA;
14949             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14950         } else {
14951             *p++ = '-';
14952         }
14953     } else {
14954         *p++ = '-';
14955     }
14956     *p++ = ' ';
14957   }
14958   }
14959
14960     /* [HGM] find reversible plies */
14961     {   int i = 0, j=move;
14962
14963         if (appData.debugMode) { int k;
14964             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14965             for(k=backwardMostMove; k<=forwardMostMove; k++)
14966                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14967
14968         }
14969
14970         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14971         if( j == backwardMostMove ) i += initialRulePlies;
14972         sprintf(p, "%d ", i);
14973         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14974     }
14975     /* Fullmove number */
14976     sprintf(p, "%d", (move / 2) + 1);
14977
14978     return StrSave(buf);
14979 }
14980
14981 Boolean
14982 ParseFEN(board, blackPlaysFirst, fen)
14983     Board board;
14984      int *blackPlaysFirst;
14985      char *fen;
14986 {
14987     int i, j;
14988     char *p, c;
14989     int emptycount;
14990     ChessSquare piece;
14991
14992     p = fen;
14993
14994     /* [HGM] by default clear Crazyhouse holdings, if present */
14995     if(gameInfo.holdingsWidth) {
14996        for(i=0; i<BOARD_HEIGHT; i++) {
14997            board[i][0]             = EmptySquare; /* black holdings */
14998            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14999            board[i][1]             = (ChessSquare) 0; /* black counts */
15000            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15001        }
15002     }
15003
15004     /* Piece placement data */
15005     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15006         j = 0;
15007         for (;;) {
15008             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15009                 if (*p == '/') p++;
15010                 emptycount = gameInfo.boardWidth - j;
15011                 while (emptycount--)
15012                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15013                 break;
15014 #if(BOARD_FILES >= 10)
15015             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15016                 p++; emptycount=10;
15017                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15018                 while (emptycount--)
15019                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15020 #endif
15021             } else if (isdigit(*p)) {
15022                 emptycount = *p++ - '0';
15023                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15024                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15025                 while (emptycount--)
15026                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15027             } else if (*p == '+' || isalpha(*p)) {
15028                 if (j >= gameInfo.boardWidth) return FALSE;
15029                 if(*p=='+') {
15030                     piece = CharToPiece(*++p);
15031                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15032                     piece = (ChessSquare) (PROMOTED piece ); p++;
15033                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15034                 } else piece = CharToPiece(*p++);
15035
15036                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15037                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15038                     piece = (ChessSquare) (PROMOTED piece);
15039                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15040                     p++;
15041                 }
15042                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15043             } else {
15044                 return FALSE;
15045             }
15046         }
15047     }
15048     while (*p == '/' || *p == ' ') p++;
15049
15050     /* [HGM] look for Crazyhouse holdings here */
15051     while(*p==' ') p++;
15052     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15053         if(*p == '[') p++;
15054         if(*p == '-' ) p++; /* empty holdings */ else {
15055             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15056             /* if we would allow FEN reading to set board size, we would   */
15057             /* have to add holdings and shift the board read so far here   */
15058             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15059                 p++;
15060                 if((int) piece >= (int) BlackPawn ) {
15061                     i = (int)piece - (int)BlackPawn;
15062                     i = PieceToNumber((ChessSquare)i);
15063                     if( i >= gameInfo.holdingsSize ) return FALSE;
15064                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15065                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15066                 } else {
15067                     i = (int)piece - (int)WhitePawn;
15068                     i = PieceToNumber((ChessSquare)i);
15069                     if( i >= gameInfo.holdingsSize ) return FALSE;
15070                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15071                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15072                 }
15073             }
15074         }
15075         if(*p == ']') p++;
15076     }
15077
15078     while(*p == ' ') p++;
15079
15080     /* Active color */
15081     c = *p++;
15082     if(appData.colorNickNames) {
15083       if( c == appData.colorNickNames[0] ) c = 'w'; else
15084       if( c == appData.colorNickNames[1] ) c = 'b';
15085     }
15086     switch (c) {
15087       case 'w':
15088         *blackPlaysFirst = FALSE;
15089         break;
15090       case 'b':
15091         *blackPlaysFirst = TRUE;
15092         break;
15093       default:
15094         return FALSE;
15095     }
15096
15097     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15098     /* return the extra info in global variiables             */
15099
15100     /* set defaults in case FEN is incomplete */
15101     board[EP_STATUS] = EP_UNKNOWN;
15102     for(i=0; i<nrCastlingRights; i++ ) {
15103         board[CASTLING][i] =
15104             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15105     }   /* assume possible unless obviously impossible */
15106     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15107     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15108     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15109                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15110     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15111     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15112     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15113                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15114     FENrulePlies = 0;
15115
15116     while(*p==' ') p++;
15117     if(nrCastlingRights) {
15118       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15119           /* castling indicator present, so default becomes no castlings */
15120           for(i=0; i<nrCastlingRights; i++ ) {
15121                  board[CASTLING][i] = NoRights;
15122           }
15123       }
15124       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15125              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15126              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15127              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15128         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15129
15130         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15131             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15132             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15133         }
15134         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15135             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15136         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15137                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15138         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15139                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15140         switch(c) {
15141           case'K':
15142               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15143               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15144               board[CASTLING][2] = whiteKingFile;
15145               break;
15146           case'Q':
15147               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15148               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15149               board[CASTLING][2] = whiteKingFile;
15150               break;
15151           case'k':
15152               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15153               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15154               board[CASTLING][5] = blackKingFile;
15155               break;
15156           case'q':
15157               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15158               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15159               board[CASTLING][5] = blackKingFile;
15160           case '-':
15161               break;
15162           default: /* FRC castlings */
15163               if(c >= 'a') { /* black rights */
15164                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15165                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15166                   if(i == BOARD_RGHT) break;
15167                   board[CASTLING][5] = i;
15168                   c -= AAA;
15169                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15170                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15171                   if(c > i)
15172                       board[CASTLING][3] = c;
15173                   else
15174                       board[CASTLING][4] = c;
15175               } else { /* white rights */
15176                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15177                     if(board[0][i] == WhiteKing) break;
15178                   if(i == BOARD_RGHT) break;
15179                   board[CASTLING][2] = i;
15180                   c -= AAA - 'a' + 'A';
15181                   if(board[0][c] >= WhiteKing) break;
15182                   if(c > i)
15183                       board[CASTLING][0] = c;
15184                   else
15185                       board[CASTLING][1] = c;
15186               }
15187         }
15188       }
15189       for(i=0; i<nrCastlingRights; i++)
15190         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15191     if (appData.debugMode) {
15192         fprintf(debugFP, "FEN castling rights:");
15193         for(i=0; i<nrCastlingRights; i++)
15194         fprintf(debugFP, " %d", board[CASTLING][i]);
15195         fprintf(debugFP, "\n");
15196     }
15197
15198       while(*p==' ') p++;
15199     }
15200
15201     /* read e.p. field in games that know e.p. capture */
15202     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15203        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15204       if(*p=='-') {
15205         p++; board[EP_STATUS] = EP_NONE;
15206       } else {
15207          char c = *p++ - AAA;
15208
15209          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15210          if(*p >= '0' && *p <='9') p++;
15211          board[EP_STATUS] = c;
15212       }
15213     }
15214
15215
15216     if(sscanf(p, "%d", &i) == 1) {
15217         FENrulePlies = i; /* 50-move ply counter */
15218         /* (The move number is still ignored)    */
15219     }
15220
15221     return TRUE;
15222 }
15223
15224 void
15225 EditPositionPasteFEN(char *fen)
15226 {
15227   if (fen != NULL) {
15228     Board initial_position;
15229
15230     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15231       DisplayError(_("Bad FEN position in clipboard"), 0);
15232       return ;
15233     } else {
15234       int savedBlackPlaysFirst = blackPlaysFirst;
15235       EditPositionEvent();
15236       blackPlaysFirst = savedBlackPlaysFirst;
15237       CopyBoard(boards[0], initial_position);
15238       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15239       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15240       DisplayBothClocks();
15241       DrawPosition(FALSE, boards[currentMove]);
15242     }
15243   }
15244 }
15245
15246 static char cseq[12] = "\\   ";
15247
15248 Boolean set_cont_sequence(char *new_seq)
15249 {
15250     int len;
15251     Boolean ret;
15252
15253     // handle bad attempts to set the sequence
15254         if (!new_seq)
15255                 return 0; // acceptable error - no debug
15256
15257     len = strlen(new_seq);
15258     ret = (len > 0) && (len < sizeof(cseq));
15259     if (ret)
15260       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15261     else if (appData.debugMode)
15262       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15263     return ret;
15264 }
15265
15266 /*
15267     reformat a source message so words don't cross the width boundary.  internal
15268     newlines are not removed.  returns the wrapped size (no null character unless
15269     included in source message).  If dest is NULL, only calculate the size required
15270     for the dest buffer.  lp argument indicats line position upon entry, and it's
15271     passed back upon exit.
15272 */
15273 int wrap(char *dest, char *src, int count, int width, int *lp)
15274 {
15275     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15276
15277     cseq_len = strlen(cseq);
15278     old_line = line = *lp;
15279     ansi = len = clen = 0;
15280
15281     for (i=0; i < count; i++)
15282     {
15283         if (src[i] == '\033')
15284             ansi = 1;
15285
15286         // if we hit the width, back up
15287         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15288         {
15289             // store i & len in case the word is too long
15290             old_i = i, old_len = len;
15291
15292             // find the end of the last word
15293             while (i && src[i] != ' ' && src[i] != '\n')
15294             {
15295                 i--;
15296                 len--;
15297             }
15298
15299             // word too long?  restore i & len before splitting it
15300             if ((old_i-i+clen) >= width)
15301             {
15302                 i = old_i;
15303                 len = old_len;
15304             }
15305
15306             // extra space?
15307             if (i && src[i-1] == ' ')
15308                 len--;
15309
15310             if (src[i] != ' ' && src[i] != '\n')
15311             {
15312                 i--;
15313                 if (len)
15314                     len--;
15315             }
15316
15317             // now append the newline and continuation sequence
15318             if (dest)
15319                 dest[len] = '\n';
15320             len++;
15321             if (dest)
15322                 strncpy(dest+len, cseq, cseq_len);
15323             len += cseq_len;
15324             line = cseq_len;
15325             clen = cseq_len;
15326             continue;
15327         }
15328
15329         if (dest)
15330             dest[len] = src[i];
15331         len++;
15332         if (!ansi)
15333             line++;
15334         if (src[i] == '\n')
15335             line = 0;
15336         if (src[i] == 'm')
15337             ansi = 0;
15338     }
15339     if (dest && appData.debugMode)
15340     {
15341         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15342             count, width, line, len, *lp);
15343         show_bytes(debugFP, src, count);
15344         fprintf(debugFP, "\ndest: ");
15345         show_bytes(debugFP, dest, len);
15346         fprintf(debugFP, "\n");
15347     }
15348     *lp = dest ? line : old_line;
15349
15350     return len;
15351 }
15352
15353 // [HGM] vari: routines for shelving variations
15354
15355 void
15356 PushTail(int firstMove, int lastMove)
15357 {
15358         int i, j, nrMoves = lastMove - firstMove;
15359
15360         if(appData.icsActive) { // only in local mode
15361                 forwardMostMove = currentMove; // mimic old ICS behavior
15362                 return;
15363         }
15364         if(storedGames >= MAX_VARIATIONS-1) return;
15365
15366         // push current tail of game on stack
15367         savedResult[storedGames] = gameInfo.result;
15368         savedDetails[storedGames] = gameInfo.resultDetails;
15369         gameInfo.resultDetails = NULL;
15370         savedFirst[storedGames] = firstMove;
15371         savedLast [storedGames] = lastMove;
15372         savedFramePtr[storedGames] = framePtr;
15373         framePtr -= nrMoves; // reserve space for the boards
15374         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15375             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15376             for(j=0; j<MOVE_LEN; j++)
15377                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15378             for(j=0; j<2*MOVE_LEN; j++)
15379                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15380             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15381             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15382             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15383             pvInfoList[firstMove+i-1].depth = 0;
15384             commentList[framePtr+i] = commentList[firstMove+i];
15385             commentList[firstMove+i] = NULL;
15386         }
15387
15388         storedGames++;
15389         forwardMostMove = firstMove; // truncate game so we can start variation
15390         if(storedGames == 1) GreyRevert(FALSE);
15391 }
15392
15393 Boolean
15394 PopTail(Boolean annotate)
15395 {
15396         int i, j, nrMoves;
15397         char buf[8000], moveBuf[20];
15398
15399         if(appData.icsActive) return FALSE; // only in local mode
15400         if(!storedGames) return FALSE; // sanity
15401         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15402
15403         storedGames--;
15404         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15405         nrMoves = savedLast[storedGames] - currentMove;
15406         if(annotate) {
15407                 int cnt = 10;
15408                 if(!WhiteOnMove(currentMove))
15409                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15410                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15411                 for(i=currentMove; i<forwardMostMove; i++) {
15412                         if(WhiteOnMove(i))
15413                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15414                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15415                         strcat(buf, moveBuf);
15416                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15417                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15418                 }
15419                 strcat(buf, ")");
15420         }
15421         for(i=1; i<=nrMoves; i++) { // copy last variation back
15422             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15423             for(j=0; j<MOVE_LEN; j++)
15424                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15425             for(j=0; j<2*MOVE_LEN; j++)
15426                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15427             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15428             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15429             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15430             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15431             commentList[currentMove+i] = commentList[framePtr+i];
15432             commentList[framePtr+i] = NULL;
15433         }
15434         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15435         framePtr = savedFramePtr[storedGames];
15436         gameInfo.result = savedResult[storedGames];
15437         if(gameInfo.resultDetails != NULL) {
15438             free(gameInfo.resultDetails);
15439       }
15440         gameInfo.resultDetails = savedDetails[storedGames];
15441         forwardMostMove = currentMove + nrMoves;
15442         if(storedGames == 0) GreyRevert(TRUE);
15443         return TRUE;
15444 }
15445
15446 void
15447 CleanupTail()
15448 {       // remove all shelved variations
15449         int i;
15450         for(i=0; i<storedGames; i++) {
15451             if(savedDetails[i])
15452                 free(savedDetails[i]);
15453             savedDetails[i] = NULL;
15454         }
15455         for(i=framePtr; i<MAX_MOVES; i++) {
15456                 if(commentList[i]) free(commentList[i]);
15457                 commentList[i] = NULL;
15458         }
15459         framePtr = MAX_MOVES-1;
15460         storedGames = 0;
15461 }
15462
15463 void
15464 LoadVariation(int index, char *text)
15465 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15466         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15467         int level = 0, move;
15468
15469         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15470         // first find outermost bracketing variation
15471         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15472             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15473                 if(*p == '{') wait = '}'; else
15474                 if(*p == '[') wait = ']'; else
15475                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15476                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15477             }
15478             if(*p == wait) wait = NULLCHAR; // closing ]} found
15479             p++;
15480         }
15481         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15482         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15483         end[1] = NULLCHAR; // clip off comment beyond variation
15484         ToNrEvent(currentMove-1);
15485         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15486         // kludge: use ParsePV() to append variation to game
15487         move = currentMove;
15488         ParsePV(start, TRUE);
15489         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15490         ClearPremoveHighlights();
15491         CommentPopDown();
15492         ToNrEvent(currentMove+1);
15493 }
15494