4044175c6f48d0ae6abc72eae6a84abbce9a0d46
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
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(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && 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 && gameInfo.variant != VariantSuper
4372                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4373               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4374                 ChessSquare old, new = boards[moveNum][k][j];
4375                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4376                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4377                   if(old == new) continue;
4378                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4379                   else if(new == WhiteWazir || new == BlackWazir) {
4380                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4381                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4382                       else boards[moveNum][k][j] = old; // preserve type of Gold
4383                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4384                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4385               }
4386           } else {
4387             /* Move from ICS was illegal!?  Punt. */
4388             if (appData.debugMode) {
4389               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4390               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4391             }
4392             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4393             strcat(parseList[moveNum - 1], " ");
4394             strcat(parseList[moveNum - 1], elapsed_time);
4395             moveList[moveNum - 1][0] = NULLCHAR;
4396             fromX = fromY = toX = toY = -1;
4397           }
4398         }
4399   if (appData.debugMode) {
4400     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4401     setbuf(debugFP, NULL);
4402   }
4403
4404 #if ZIPPY
4405         /* Send move to chess program (BEFORE animating it). */
4406         if (appData.zippyPlay && !newGame && newMove &&
4407            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4408
4409             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4410                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4411                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4412                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4413                             move_str);
4414                     DisplayError(str, 0);
4415                 } else {
4416                     if (first.sendTime) {
4417                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4418                     }
4419                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4420                     if (firstMove && !bookHit) {
4421                         firstMove = FALSE;
4422                         if (first.useColors) {
4423                           SendToProgram(gameMode == IcsPlayingWhite ?
4424                                         "white\ngo\n" :
4425                                         "black\ngo\n", &first);
4426                         } else {
4427                           SendToProgram("go\n", &first);
4428                         }
4429                         first.maybeThinking = TRUE;
4430                     }
4431                 }
4432             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4433               if (moveList[moveNum - 1][0] == NULLCHAR) {
4434                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4435                 DisplayError(str, 0);
4436               } else {
4437                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4438                 SendMoveToProgram(moveNum - 1, &first);
4439               }
4440             }
4441         }
4442 #endif
4443     }
4444
4445     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4446         /* If move comes from a remote source, animate it.  If it
4447            isn't remote, it will have already been animated. */
4448         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4449             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4450         }
4451         if (!pausing && appData.highlightLastMove) {
4452             SetHighlights(fromX, fromY, toX, toY);
4453         }
4454     }
4455
4456     /* Start the clocks */
4457     whiteFlag = blackFlag = FALSE;
4458     appData.clockMode = !(basetime == 0 && increment == 0);
4459     if (ticking == 0) {
4460       ics_clock_paused = TRUE;
4461       StopClocks();
4462     } else if (ticking == 1) {
4463       ics_clock_paused = FALSE;
4464     }
4465     if (gameMode == IcsIdle ||
4466         relation == RELATION_OBSERVING_STATIC ||
4467         relation == RELATION_EXAMINING ||
4468         ics_clock_paused)
4469       DisplayBothClocks();
4470     else
4471       StartClocks();
4472
4473     /* Display opponents and material strengths */
4474     if (gameInfo.variant != VariantBughouse &&
4475         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4476         if (tinyLayout || smallLayout) {
4477             if(gameInfo.variant == VariantNormal)
4478               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4479                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4480                     basetime, increment);
4481             else
4482               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4483                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4484                     basetime, increment, (int) gameInfo.variant);
4485         } else {
4486             if(gameInfo.variant == VariantNormal)
4487               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4488                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4489                     basetime, increment);
4490             else
4491               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4492                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4493                     basetime, increment, VariantName(gameInfo.variant));
4494         }
4495         DisplayTitle(str);
4496   if (appData.debugMode) {
4497     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4498   }
4499     }
4500
4501
4502     /* Display the board */
4503     if (!pausing && !appData.noGUI) {
4504
4505       if (appData.premove)
4506           if (!gotPremove ||
4507              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4508              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4509               ClearPremoveHighlights();
4510
4511       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4512         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4513       DrawPosition(j, boards[currentMove]);
4514
4515       DisplayMove(moveNum - 1);
4516       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4517             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4518               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4519         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4520       }
4521     }
4522
4523     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4524 #if ZIPPY
4525     if(bookHit) { // [HGM] book: simulate book reply
4526         static char bookMove[MSG_SIZ]; // a bit generous?
4527
4528         programStats.nodes = programStats.depth = programStats.time =
4529         programStats.score = programStats.got_only_move = 0;
4530         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4531
4532         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4533         strcat(bookMove, bookHit);
4534         HandleMachineMove(bookMove, &first);
4535     }
4536 #endif
4537 }
4538
4539 void
4540 GetMoveListEvent()
4541 {
4542     char buf[MSG_SIZ];
4543     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4544         ics_getting_history = H_REQUESTED;
4545         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4546         SendToICS(buf);
4547     }
4548 }
4549
4550 void
4551 AnalysisPeriodicEvent(force)
4552      int force;
4553 {
4554     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4555          && !force) || !appData.periodicUpdates)
4556       return;
4557
4558     /* Send . command to Crafty to collect stats */
4559     SendToProgram(".\n", &first);
4560
4561     /* Don't send another until we get a response (this makes
4562        us stop sending to old Crafty's which don't understand
4563        the "." command (sending illegal cmds resets node count & time,
4564        which looks bad)) */
4565     programStats.ok_to_send = 0;
4566 }
4567
4568 void ics_update_width(new_width)
4569         int new_width;
4570 {
4571         ics_printf("set width %d\n", new_width);
4572 }
4573
4574 void
4575 SendMoveToProgram(moveNum, cps)
4576      int moveNum;
4577      ChessProgramState *cps;
4578 {
4579     char buf[MSG_SIZ];
4580
4581     if (cps->useUsermove) {
4582       SendToProgram("usermove ", cps);
4583     }
4584     if (cps->useSAN) {
4585       char *space;
4586       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4587         int len = space - parseList[moveNum];
4588         memcpy(buf, parseList[moveNum], len);
4589         buf[len++] = '\n';
4590         buf[len] = NULLCHAR;
4591       } else {
4592         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4593       }
4594       SendToProgram(buf, cps);
4595     } else {
4596       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4597         AlphaRank(moveList[moveNum], 4);
4598         SendToProgram(moveList[moveNum], cps);
4599         AlphaRank(moveList[moveNum], 4); // and back
4600       } else
4601       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4602        * the engine. It would be nice to have a better way to identify castle
4603        * moves here. */
4604       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4605                                                                          && cps->useOOCastle) {
4606         int fromX = moveList[moveNum][0] - AAA;
4607         int fromY = moveList[moveNum][1] - ONE;
4608         int toX = moveList[moveNum][2] - AAA;
4609         int toY = moveList[moveNum][3] - ONE;
4610         if((boards[moveNum][fromY][fromX] == WhiteKing
4611             && boards[moveNum][toY][toX] == WhiteRook)
4612            || (boards[moveNum][fromY][fromX] == BlackKing
4613                && boards[moveNum][toY][toX] == BlackRook)) {
4614           if(toX > fromX) SendToProgram("O-O\n", cps);
4615           else SendToProgram("O-O-O\n", cps);
4616         }
4617         else SendToProgram(moveList[moveNum], cps);
4618       }
4619       else SendToProgram(moveList[moveNum], cps);
4620       /* End of additions by Tord */
4621     }
4622
4623     /* [HGM] setting up the opening has brought engine in force mode! */
4624     /*       Send 'go' if we are in a mode where machine should play. */
4625     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4626         (gameMode == TwoMachinesPlay   ||
4627 #if ZIPPY
4628          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4629 #endif
4630          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4631         SendToProgram("go\n", cps);
4632   if (appData.debugMode) {
4633     fprintf(debugFP, "(extra)\n");
4634   }
4635     }
4636     setboardSpoiledMachineBlack = 0;
4637 }
4638
4639 void
4640 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4641      ChessMove moveType;
4642      int fromX, fromY, toX, toY;
4643      char promoChar;
4644 {
4645     char user_move[MSG_SIZ];
4646
4647     switch (moveType) {
4648       default:
4649         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4650                 (int)moveType, fromX, fromY, toX, toY);
4651         DisplayError(user_move + strlen("say "), 0);
4652         break;
4653       case WhiteKingSideCastle:
4654       case BlackKingSideCastle:
4655       case WhiteQueenSideCastleWild:
4656       case BlackQueenSideCastleWild:
4657       /* PUSH Fabien */
4658       case WhiteHSideCastleFR:
4659       case BlackHSideCastleFR:
4660       /* POP Fabien */
4661         snprintf(user_move, MSG_SIZ, "o-o\n");
4662         break;
4663       case WhiteQueenSideCastle:
4664       case BlackQueenSideCastle:
4665       case WhiteKingSideCastleWild:
4666       case BlackKingSideCastleWild:
4667       /* PUSH Fabien */
4668       case WhiteASideCastleFR:
4669       case BlackASideCastleFR:
4670       /* POP Fabien */
4671         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4672         break;
4673       case WhiteNonPromotion:
4674       case BlackNonPromotion:
4675         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4676         break;
4677       case WhitePromotion:
4678       case BlackPromotion:
4679         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4680           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4681                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4682                 PieceToChar(WhiteFerz));
4683         else if(gameInfo.variant == VariantGreat)
4684           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4685                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4686                 PieceToChar(WhiteMan));
4687         else
4688           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4689                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4690                 promoChar);
4691         break;
4692       case WhiteDrop:
4693       case BlackDrop:
4694       drop:
4695         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4696                  ToUpper(PieceToChar((ChessSquare) fromX)),
4697                  AAA + toX, ONE + toY);
4698         break;
4699       case IllegalMove:  /* could be a variant we don't quite understand */
4700         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4701       case NormalMove:
4702       case WhiteCapturesEnPassant:
4703       case BlackCapturesEnPassant:
4704         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4705                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4706         break;
4707     }
4708     SendToICS(user_move);
4709     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4710         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4711 }
4712
4713 void
4714 UploadGameEvent()
4715 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4716     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4717     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4718     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4719         DisplayError("You cannot do this while you are playing or observing", 0);
4720         return;
4721     }
4722     if(gameMode != IcsExamining) { // is this ever not the case?
4723         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4724
4725         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4726           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4727         } else { // on FICS we must first go to general examine mode
4728           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4729         }
4730         if(gameInfo.variant != VariantNormal) {
4731             // try figure out wild number, as xboard names are not always valid on ICS
4732             for(i=1; i<=36; i++) {
4733               snprintf(buf, MSG_SIZ, "wild/%d", i);
4734                 if(StringToVariant(buf) == gameInfo.variant) break;
4735             }
4736             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4737             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4738             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4739         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4740         SendToICS(ics_prefix);
4741         SendToICS(buf);
4742         if(startedFromSetupPosition || backwardMostMove != 0) {
4743           fen = PositionToFEN(backwardMostMove, NULL);
4744           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4745             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4746             SendToICS(buf);
4747           } else { // FICS: everything has to set by separate bsetup commands
4748             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4749             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4750             SendToICS(buf);
4751             if(!WhiteOnMove(backwardMostMove)) {
4752                 SendToICS("bsetup tomove black\n");
4753             }
4754             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4755             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4756             SendToICS(buf);
4757             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4758             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4759             SendToICS(buf);
4760             i = boards[backwardMostMove][EP_STATUS];
4761             if(i >= 0) { // set e.p.
4762               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4763                 SendToICS(buf);
4764             }
4765             bsetup++;
4766           }
4767         }
4768       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4769             SendToICS("bsetup done\n"); // switch to normal examining.
4770     }
4771     for(i = backwardMostMove; i<last; i++) {
4772         char buf[20];
4773         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4774         SendToICS(buf);
4775     }
4776     SendToICS(ics_prefix);
4777     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4778 }
4779
4780 void
4781 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4782      int rf, ff, rt, ft;
4783      char promoChar;
4784      char move[7];
4785 {
4786     if (rf == DROP_RANK) {
4787       sprintf(move, "%c@%c%c\n",
4788                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4789     } else {
4790         if (promoChar == 'x' || promoChar == NULLCHAR) {
4791           sprintf(move, "%c%c%c%c\n",
4792                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4793         } else {
4794             sprintf(move, "%c%c%c%c%c\n",
4795                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4796         }
4797     }
4798 }
4799
4800 void
4801 ProcessICSInitScript(f)
4802      FILE *f;
4803 {
4804     char buf[MSG_SIZ];
4805
4806     while (fgets(buf, MSG_SIZ, f)) {
4807         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4808     }
4809
4810     fclose(f);
4811 }
4812
4813
4814 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4815 void
4816 AlphaRank(char *move, int n)
4817 {
4818 //    char *p = move, c; int x, y;
4819
4820     if (appData.debugMode) {
4821         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4822     }
4823
4824     if(move[1]=='*' &&
4825        move[2]>='0' && move[2]<='9' &&
4826        move[3]>='a' && move[3]<='x'    ) {
4827         move[1] = '@';
4828         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4829         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4830     } else
4831     if(move[0]>='0' && move[0]<='9' &&
4832        move[1]>='a' && move[1]<='x' &&
4833        move[2]>='0' && move[2]<='9' &&
4834        move[3]>='a' && move[3]<='x'    ) {
4835         /* input move, Shogi -> normal */
4836         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4837         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4838         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4839         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4840     } else
4841     if(move[1]=='@' &&
4842        move[3]>='0' && move[3]<='9' &&
4843        move[2]>='a' && move[2]<='x'    ) {
4844         move[1] = '*';
4845         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4846         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4847     } else
4848     if(
4849        move[0]>='a' && move[0]<='x' &&
4850        move[3]>='0' && move[3]<='9' &&
4851        move[2]>='a' && move[2]<='x'    ) {
4852          /* output move, normal -> Shogi */
4853         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4854         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4855         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4856         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4857         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4858     }
4859     if (appData.debugMode) {
4860         fprintf(debugFP, "   out = '%s'\n", move);
4861     }
4862 }
4863
4864 char yy_textstr[8000];
4865
4866 /* Parser for moves from gnuchess, ICS, or user typein box */
4867 Boolean
4868 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4869      char *move;
4870      int moveNum;
4871      ChessMove *moveType;
4872      int *fromX, *fromY, *toX, *toY;
4873      char *promoChar;
4874 {
4875     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4876
4877     switch (*moveType) {
4878       case WhitePromotion:
4879       case BlackPromotion:
4880       case WhiteNonPromotion:
4881       case BlackNonPromotion:
4882       case NormalMove:
4883       case WhiteCapturesEnPassant:
4884       case BlackCapturesEnPassant:
4885       case WhiteKingSideCastle:
4886       case WhiteQueenSideCastle:
4887       case BlackKingSideCastle:
4888       case BlackQueenSideCastle:
4889       case WhiteKingSideCastleWild:
4890       case WhiteQueenSideCastleWild:
4891       case BlackKingSideCastleWild:
4892       case BlackQueenSideCastleWild:
4893       /* Code added by Tord: */
4894       case WhiteHSideCastleFR:
4895       case WhiteASideCastleFR:
4896       case BlackHSideCastleFR:
4897       case BlackASideCastleFR:
4898       /* End of code added by Tord */
4899       case IllegalMove:         /* bug or odd chess variant */
4900         *fromX = currentMoveString[0] - AAA;
4901         *fromY = currentMoveString[1] - ONE;
4902         *toX = currentMoveString[2] - AAA;
4903         *toY = currentMoveString[3] - ONE;
4904         *promoChar = currentMoveString[4];
4905         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4906             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4907     if (appData.debugMode) {
4908         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4909     }
4910             *fromX = *fromY = *toX = *toY = 0;
4911             return FALSE;
4912         }
4913         if (appData.testLegality) {
4914           return (*moveType != IllegalMove);
4915         } else {
4916           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4917                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4918         }
4919
4920       case WhiteDrop:
4921       case BlackDrop:
4922         *fromX = *moveType == WhiteDrop ?
4923           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4924           (int) CharToPiece(ToLower(currentMoveString[0]));
4925         *fromY = DROP_RANK;
4926         *toX = currentMoveString[2] - AAA;
4927         *toY = currentMoveString[3] - ONE;
4928         *promoChar = NULLCHAR;
4929         return TRUE;
4930
4931       case AmbiguousMove:
4932       case ImpossibleMove:
4933       case EndOfFile:
4934       case ElapsedTime:
4935       case Comment:
4936       case PGNTag:
4937       case NAG:
4938       case WhiteWins:
4939       case BlackWins:
4940       case GameIsDrawn:
4941       default:
4942     if (appData.debugMode) {
4943         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4944     }
4945         /* bug? */
4946         *fromX = *fromY = *toX = *toY = 0;
4947         *promoChar = NULLCHAR;
4948         return FALSE;
4949     }
4950 }
4951
4952
4953 void
4954 ParsePV(char *pv, Boolean storeComments)
4955 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4956   int fromX, fromY, toX, toY; char promoChar;
4957   ChessMove moveType;
4958   Boolean valid;
4959   int nr = 0;
4960
4961   endPV = forwardMostMove;
4962   do {
4963     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4964     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4965     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4966 if(appData.debugMode){
4967 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);
4968 }
4969     if(!valid && nr == 0 &&
4970        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4971         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4972         // Hande case where played move is different from leading PV move
4973         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4974         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4975         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4976         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4977           endPV += 2; // if position different, keep this
4978           moveList[endPV-1][0] = fromX + AAA;
4979           moveList[endPV-1][1] = fromY + ONE;
4980           moveList[endPV-1][2] = toX + AAA;
4981           moveList[endPV-1][3] = toY + ONE;
4982           parseList[endPV-1][0] = NULLCHAR;
4983           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4984         }
4985       }
4986     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4987     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4988     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4989     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4990         valid++; // allow comments in PV
4991         continue;
4992     }
4993     nr++;
4994     if(endPV+1 > framePtr) break; // no space, truncate
4995     if(!valid) break;
4996     endPV++;
4997     CopyBoard(boards[endPV], boards[endPV-1]);
4998     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4999     moveList[endPV-1][0] = fromX + AAA;
5000     moveList[endPV-1][1] = fromY + ONE;
5001     moveList[endPV-1][2] = toX + AAA;
5002     moveList[endPV-1][3] = toY + ONE;
5003     moveList[endPV-1][4] = promoChar;
5004     moveList[endPV-1][5] = NULLCHAR;
5005     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5006     if(storeComments)
5007         CoordsToAlgebraic(boards[endPV - 1],
5008                              PosFlags(endPV - 1),
5009                              fromY, fromX, toY, toX, promoChar,
5010                              parseList[endPV - 1]);
5011     else
5012         parseList[endPV-1][0] = NULLCHAR;
5013   } while(valid);
5014   currentMove = endPV;
5015   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5016   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5017                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5018   DrawPosition(TRUE, boards[currentMove]);
5019 }
5020
5021 static int lastX, lastY;
5022
5023 Boolean
5024 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5025 {
5026         int startPV;
5027         char *p;
5028
5029         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5030         lastX = x; lastY = y;
5031         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5032         startPV = index;
5033         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5034         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5035         index = startPV;
5036         do{ while(buf[index] && buf[index] != '\n') index++;
5037         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5038         buf[index] = 0;
5039         ParsePV(buf+startPV, FALSE);
5040         *start = startPV; *end = index-1;
5041         return TRUE;
5042 }
5043
5044 Boolean
5045 LoadPV(int x, int y)
5046 { // called on right mouse click to load PV
5047   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5048   lastX = x; lastY = y;
5049   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5050   return TRUE;
5051 }
5052
5053 void
5054 UnLoadPV()
5055 {
5056   if(endPV < 0) return;
5057   endPV = -1;
5058   currentMove = forwardMostMove;
5059   ClearPremoveHighlights();
5060   DrawPosition(TRUE, boards[currentMove]);
5061 }
5062
5063 void
5064 MovePV(int x, int y, int h)
5065 { // step through PV based on mouse coordinates (called on mouse move)
5066   int margin = h>>3, step = 0;
5067
5068   if(endPV < 0) return;
5069   // we must somehow check if right button is still down (might be released off board!)
5070   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5071   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5072   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5073   if(!step) return;
5074   lastX = x; lastY = y;
5075   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5076   currentMove += step;
5077   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5078   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5079                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5080   DrawPosition(FALSE, boards[currentMove]);
5081 }
5082
5083
5084 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5085 // All positions will have equal probability, but the current method will not provide a unique
5086 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5087 #define DARK 1
5088 #define LITE 2
5089 #define ANY 3
5090
5091 int squaresLeft[4];
5092 int piecesLeft[(int)BlackPawn];
5093 int seed, nrOfShuffles;
5094
5095 void GetPositionNumber()
5096 {       // sets global variable seed
5097         int i;
5098
5099         seed = appData.defaultFrcPosition;
5100         if(seed < 0) { // randomize based on time for negative FRC position numbers
5101                 for(i=0; i<50; i++) seed += random();
5102                 seed = random() ^ random() >> 8 ^ random() << 8;
5103                 if(seed<0) seed = -seed;
5104         }
5105 }
5106
5107 int put(Board board, int pieceType, int rank, int n, int shade)
5108 // put the piece on the (n-1)-th empty squares of the given shade
5109 {
5110         int i;
5111
5112         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5113                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5114                         board[rank][i] = (ChessSquare) pieceType;
5115                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5116                         squaresLeft[ANY]--;
5117                         piecesLeft[pieceType]--;
5118                         return i;
5119                 }
5120         }
5121         return -1;
5122 }
5123
5124
5125 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5126 // calculate where the next piece goes, (any empty square), and put it there
5127 {
5128         int i;
5129
5130         i = seed % squaresLeft[shade];
5131         nrOfShuffles *= squaresLeft[shade];
5132         seed /= squaresLeft[shade];
5133         put(board, pieceType, rank, i, shade);
5134 }
5135
5136 void AddTwoPieces(Board board, int pieceType, int rank)
5137 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5138 {
5139         int i, n=squaresLeft[ANY], j=n-1, k;
5140
5141         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5142         i = seed % k;  // pick one
5143         nrOfShuffles *= k;
5144         seed /= k;
5145         while(i >= j) i -= j--;
5146         j = n - 1 - j; i += j;
5147         put(board, pieceType, rank, j, ANY);
5148         put(board, pieceType, rank, i, ANY);
5149 }
5150
5151 void SetUpShuffle(Board board, int number)
5152 {
5153         int i, p, first=1;
5154
5155         GetPositionNumber(); nrOfShuffles = 1;
5156
5157         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5158         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5159         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5160
5161         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5162
5163         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5164             p = (int) board[0][i];
5165             if(p < (int) BlackPawn) piecesLeft[p] ++;
5166             board[0][i] = EmptySquare;
5167         }
5168
5169         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5170             // shuffles restricted to allow normal castling put KRR first
5171             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5172                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5173             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5174                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5175             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5176                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5177             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5178                 put(board, WhiteRook, 0, 0, ANY);
5179             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5180         }
5181
5182         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5183             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5184             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5185                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5186                 while(piecesLeft[p] >= 2) {
5187                     AddOnePiece(board, p, 0, LITE);
5188                     AddOnePiece(board, p, 0, DARK);
5189                 }
5190                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5191             }
5192
5193         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5194             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5195             // but we leave King and Rooks for last, to possibly obey FRC restriction
5196             if(p == (int)WhiteRook) continue;
5197             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5198             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5199         }
5200
5201         // now everything is placed, except perhaps King (Unicorn) and Rooks
5202
5203         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5204             // Last King gets castling rights
5205             while(piecesLeft[(int)WhiteUnicorn]) {
5206                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5207                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5208             }
5209
5210             while(piecesLeft[(int)WhiteKing]) {
5211                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5212                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5213             }
5214
5215
5216         } else {
5217             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5218             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5219         }
5220
5221         // Only Rooks can be left; simply place them all
5222         while(piecesLeft[(int)WhiteRook]) {
5223                 i = put(board, WhiteRook, 0, 0, ANY);
5224                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5225                         if(first) {
5226                                 first=0;
5227                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5228                         }
5229                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5230                 }
5231         }
5232         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5233             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5234         }
5235
5236         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5237 }
5238
5239 int SetCharTable( char *table, const char * map )
5240 /* [HGM] moved here from winboard.c because of its general usefulness */
5241 /*       Basically a safe strcpy that uses the last character as King */
5242 {
5243     int result = FALSE; int NrPieces;
5244
5245     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5246                     && NrPieces >= 12 && !(NrPieces&1)) {
5247         int i; /* [HGM] Accept even length from 12 to 34 */
5248
5249         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5250         for( i=0; i<NrPieces/2-1; i++ ) {
5251             table[i] = map[i];
5252             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5253         }
5254         table[(int) WhiteKing]  = map[NrPieces/2-1];
5255         table[(int) BlackKing]  = map[NrPieces-1];
5256
5257         result = TRUE;
5258     }
5259
5260     return result;
5261 }
5262
5263 void Prelude(Board board)
5264 {       // [HGM] superchess: random selection of exo-pieces
5265         int i, j, k; ChessSquare p;
5266         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5267
5268         GetPositionNumber(); // use FRC position number
5269
5270         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5271             SetCharTable(pieceToChar, appData.pieceToCharTable);
5272             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5273                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5274         }
5275
5276         j = seed%4;                 seed /= 4;
5277         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5278         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5279         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5280         j = seed%3 + (seed%3 >= j); seed /= 3;
5281         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5282         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5283         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5284         j = seed%3;                 seed /= 3;
5285         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5286         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5287         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5288         j = seed%2 + (seed%2 >= j); seed /= 2;
5289         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5290         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5291         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5292         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5293         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5294         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5295         put(board, exoPieces[0],    0, 0, ANY);
5296         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5297 }
5298
5299 void
5300 InitPosition(redraw)
5301      int redraw;
5302 {
5303     ChessSquare (* pieces)[BOARD_FILES];
5304     int i, j, pawnRow, overrule,
5305     oldx = gameInfo.boardWidth,
5306     oldy = gameInfo.boardHeight,
5307     oldh = gameInfo.holdingsWidth;
5308     static int oldv;
5309
5310     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5311
5312     /* [AS] Initialize pv info list [HGM] and game status */
5313     {
5314         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5315             pvInfoList[i].depth = 0;
5316             boards[i][EP_STATUS] = EP_NONE;
5317             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5318         }
5319
5320         initialRulePlies = 0; /* 50-move counter start */
5321
5322         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5323         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5324     }
5325
5326
5327     /* [HGM] logic here is completely changed. In stead of full positions */
5328     /* the initialized data only consist of the two backranks. The switch */
5329     /* selects which one we will use, which is than copied to the Board   */
5330     /* initialPosition, which for the rest is initialized by Pawns and    */
5331     /* empty squares. This initial position is then copied to boards[0],  */
5332     /* possibly after shuffling, so that it remains available.            */
5333
5334     gameInfo.holdingsWidth = 0; /* default board sizes */
5335     gameInfo.boardWidth    = 8;
5336     gameInfo.boardHeight   = 8;
5337     gameInfo.holdingsSize  = 0;
5338     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5339     for(i=0; i<BOARD_FILES-2; i++)
5340       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5341     initialPosition[EP_STATUS] = EP_NONE;
5342     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5343     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5344          SetCharTable(pieceNickName, appData.pieceNickNames);
5345     else SetCharTable(pieceNickName, "............");
5346     pieces = FIDEArray;
5347
5348     switch (gameInfo.variant) {
5349     case VariantFischeRandom:
5350       shuffleOpenings = TRUE;
5351     default:
5352       break;
5353     case VariantShatranj:
5354       pieces = ShatranjArray;
5355       nrCastlingRights = 0;
5356       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5357       break;
5358     case VariantMakruk:
5359       pieces = makrukArray;
5360       nrCastlingRights = 0;
5361       startedFromSetupPosition = TRUE;
5362       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5363       break;
5364     case VariantTwoKings:
5365       pieces = twoKingsArray;
5366       break;
5367     case VariantCapaRandom:
5368       shuffleOpenings = TRUE;
5369     case VariantCapablanca:
5370       pieces = CapablancaArray;
5371       gameInfo.boardWidth = 10;
5372       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5373       break;
5374     case VariantGothic:
5375       pieces = GothicArray;
5376       gameInfo.boardWidth = 10;
5377       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5378       break;
5379     case VariantSChess:
5380       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5381       gameInfo.holdingsSize = 7;
5382       break;
5383     case VariantJanus:
5384       pieces = JanusArray;
5385       gameInfo.boardWidth = 10;
5386       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5387       nrCastlingRights = 6;
5388         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5389         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5390         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5391         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5392         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5393         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5394       break;
5395     case VariantFalcon:
5396       pieces = FalconArray;
5397       gameInfo.boardWidth = 10;
5398       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5399       break;
5400     case VariantXiangqi:
5401       pieces = XiangqiArray;
5402       gameInfo.boardWidth  = 9;
5403       gameInfo.boardHeight = 10;
5404       nrCastlingRights = 0;
5405       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5406       break;
5407     case VariantShogi:
5408       pieces = ShogiArray;
5409       gameInfo.boardWidth  = 9;
5410       gameInfo.boardHeight = 9;
5411       gameInfo.holdingsSize = 7;
5412       nrCastlingRights = 0;
5413       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5414       break;
5415     case VariantCourier:
5416       pieces = CourierArray;
5417       gameInfo.boardWidth  = 12;
5418       nrCastlingRights = 0;
5419       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5420       break;
5421     case VariantKnightmate:
5422       pieces = KnightmateArray;
5423       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5424       break;
5425     case VariantSpartan:
5426       pieces = SpartanArray;
5427       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5428       break;
5429     case VariantFairy:
5430       pieces = fairyArray;
5431       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5432       break;
5433     case VariantGreat:
5434       pieces = GreatArray;
5435       gameInfo.boardWidth = 10;
5436       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5437       gameInfo.holdingsSize = 8;
5438       break;
5439     case VariantSuper:
5440       pieces = FIDEArray;
5441       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5442       gameInfo.holdingsSize = 8;
5443       startedFromSetupPosition = TRUE;
5444       break;
5445     case VariantCrazyhouse:
5446     case VariantBughouse:
5447       pieces = FIDEArray;
5448       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5449       gameInfo.holdingsSize = 5;
5450       break;
5451     case VariantWildCastle:
5452       pieces = FIDEArray;
5453       /* !!?shuffle with kings guaranteed to be on d or e file */
5454       shuffleOpenings = 1;
5455       break;
5456     case VariantNoCastle:
5457       pieces = FIDEArray;
5458       nrCastlingRights = 0;
5459       /* !!?unconstrained back-rank shuffle */
5460       shuffleOpenings = 1;
5461       break;
5462     }
5463
5464     overrule = 0;
5465     if(appData.NrFiles >= 0) {
5466         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5467         gameInfo.boardWidth = appData.NrFiles;
5468     }
5469     if(appData.NrRanks >= 0) {
5470         gameInfo.boardHeight = appData.NrRanks;
5471     }
5472     if(appData.holdingsSize >= 0) {
5473         i = appData.holdingsSize;
5474         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5475         gameInfo.holdingsSize = i;
5476     }
5477     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5478     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5479         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5480
5481     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5482     if(pawnRow < 1) pawnRow = 1;
5483     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5484
5485     /* User pieceToChar list overrules defaults */
5486     if(appData.pieceToCharTable != NULL)
5487         SetCharTable(pieceToChar, appData.pieceToCharTable);
5488
5489     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5490
5491         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5492             s = (ChessSquare) 0; /* account holding counts in guard band */
5493         for( i=0; i<BOARD_HEIGHT; i++ )
5494             initialPosition[i][j] = s;
5495
5496         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5497         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5498         initialPosition[pawnRow][j] = WhitePawn;
5499         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5500         if(gameInfo.variant == VariantXiangqi) {
5501             if(j&1) {
5502                 initialPosition[pawnRow][j] =
5503                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5504                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5505                    initialPosition[2][j] = WhiteCannon;
5506                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5507                 }
5508             }
5509         }
5510         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5511     }
5512     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5513
5514             j=BOARD_LEFT+1;
5515             initialPosition[1][j] = WhiteBishop;
5516             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5517             j=BOARD_RGHT-2;
5518             initialPosition[1][j] = WhiteRook;
5519             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5520     }
5521
5522     if( nrCastlingRights == -1) {
5523         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5524         /*       This sets default castling rights from none to normal corners   */
5525         /* Variants with other castling rights must set them themselves above    */
5526         nrCastlingRights = 6;
5527
5528         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5529         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5530         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5531         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5532         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5533         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5534      }
5535
5536      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5537      if(gameInfo.variant == VariantGreat) { // promotion commoners
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5539         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5541         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5542      }
5543      if( gameInfo.variant == VariantSChess ) {
5544       initialPosition[1][0] = BlackMarshall;
5545       initialPosition[2][0] = BlackAngel;
5546       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5547       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5548       initialPosition[1][1] = initialPosition[2][1] = 
5549       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5550      }
5551   if (appData.debugMode) {
5552     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5553   }
5554     if(shuffleOpenings) {
5555         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5556         startedFromSetupPosition = TRUE;
5557     }
5558     if(startedFromPositionFile) {
5559       /* [HGM] loadPos: use PositionFile for every new game */
5560       CopyBoard(initialPosition, filePosition);
5561       for(i=0; i<nrCastlingRights; i++)
5562           initialRights[i] = filePosition[CASTLING][i];
5563       startedFromSetupPosition = TRUE;
5564     }
5565
5566     CopyBoard(boards[0], initialPosition);
5567
5568     if(oldx != gameInfo.boardWidth ||
5569        oldy != gameInfo.boardHeight ||
5570        oldv != gameInfo.variant ||
5571        oldh != gameInfo.holdingsWidth
5572                                          )
5573             InitDrawingSizes(-2 ,0);
5574
5575     oldv = gameInfo.variant;
5576     if (redraw)
5577       DrawPosition(TRUE, boards[currentMove]);
5578 }
5579
5580 void
5581 SendBoard(cps, moveNum)
5582      ChessProgramState *cps;
5583      int moveNum;
5584 {
5585     char message[MSG_SIZ];
5586
5587     if (cps->useSetboard) {
5588       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5589       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5590       SendToProgram(message, cps);
5591       free(fen);
5592
5593     } else {
5594       ChessSquare *bp;
5595       int i, j;
5596       /* Kludge to set black to move, avoiding the troublesome and now
5597        * deprecated "black" command.
5598        */
5599       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5600         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5601
5602       SendToProgram("edit\n", cps);
5603       SendToProgram("#\n", cps);
5604       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5605         bp = &boards[moveNum][i][BOARD_LEFT];
5606         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5607           if ((int) *bp < (int) BlackPawn) {
5608             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5609                     AAA + j, ONE + i);
5610             if(message[0] == '+' || message[0] == '~') {
5611               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5612                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5613                         AAA + j, ONE + i);
5614             }
5615             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5616                 message[1] = BOARD_RGHT   - 1 - j + '1';
5617                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5618             }
5619             SendToProgram(message, cps);
5620           }
5621         }
5622       }
5623
5624       SendToProgram("c\n", cps);
5625       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5626         bp = &boards[moveNum][i][BOARD_LEFT];
5627         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5628           if (((int) *bp != (int) EmptySquare)
5629               && ((int) *bp >= (int) BlackPawn)) {
5630             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5631                     AAA + j, ONE + i);
5632             if(message[0] == '+' || message[0] == '~') {
5633               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5634                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5635                         AAA + j, ONE + i);
5636             }
5637             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5638                 message[1] = BOARD_RGHT   - 1 - j + '1';
5639                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5640             }
5641             SendToProgram(message, cps);
5642           }
5643         }
5644       }
5645
5646       SendToProgram(".\n", cps);
5647     }
5648     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5649 }
5650
5651 static int autoQueen; // [HGM] oneclick
5652
5653 int
5654 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5655 {
5656     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5657     /* [HGM] add Shogi promotions */
5658     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5659     ChessSquare piece;
5660     ChessMove moveType;
5661     Boolean premove;
5662
5663     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5664     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5665
5666     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5667       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5668         return FALSE;
5669
5670     piece = boards[currentMove][fromY][fromX];
5671     if(gameInfo.variant == VariantShogi) {
5672         promotionZoneSize = BOARD_HEIGHT/3;
5673         highestPromotingPiece = (int)WhiteFerz;
5674     } else if(gameInfo.variant == VariantMakruk) {
5675         promotionZoneSize = 3;
5676     }
5677
5678     // Treat Lance as Pawn when it is not representing Amazon
5679     if(gameInfo.variant != VariantSuper) {
5680         if(piece == WhiteLance) piece = WhitePawn; else
5681         if(piece == BlackLance) piece = BlackPawn;
5682     }
5683
5684     // next weed out all moves that do not touch the promotion zone at all
5685     if((int)piece >= BlackPawn) {
5686         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5687              return FALSE;
5688         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5689     } else {
5690         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5691            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5692     }
5693
5694     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5695
5696     // weed out mandatory Shogi promotions
5697     if(gameInfo.variant == VariantShogi) {
5698         if(piece >= BlackPawn) {
5699             if(toY == 0 && piece == BlackPawn ||
5700                toY == 0 && piece == BlackQueen ||
5701                toY <= 1 && piece == BlackKnight) {
5702                 *promoChoice = '+';
5703                 return FALSE;
5704             }
5705         } else {
5706             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5707                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5708                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5709                 *promoChoice = '+';
5710                 return FALSE;
5711             }
5712         }
5713     }
5714
5715     // weed out obviously illegal Pawn moves
5716     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5717         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5718         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5719         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5720         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5721         // note we are not allowed to test for valid (non-)capture, due to premove
5722     }
5723
5724     // we either have a choice what to promote to, or (in Shogi) whether to promote
5725     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5726         *promoChoice = PieceToChar(BlackFerz);  // no choice
5727         return FALSE;
5728     }
5729     // no sense asking what we must promote to if it is going to explode...
5730     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5731         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5732         return FALSE;
5733     }
5734     if(autoQueen) { // predetermined
5735         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5736              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5737         else *promoChoice = PieceToChar(BlackQueen);
5738         return FALSE;
5739     }
5740
5741     // suppress promotion popup on illegal moves that are not premoves
5742     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5743               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5744     if(appData.testLegality && !premove) {
5745         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5746                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5747         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5748             return FALSE;
5749     }
5750
5751     return TRUE;
5752 }
5753
5754 int
5755 InPalace(row, column)
5756      int row, column;
5757 {   /* [HGM] for Xiangqi */
5758     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5759          column < (BOARD_WIDTH + 4)/2 &&
5760          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5761     return FALSE;
5762 }
5763
5764 int
5765 PieceForSquare (x, y)
5766      int x;
5767      int y;
5768 {
5769   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5770      return -1;
5771   else
5772      return boards[currentMove][y][x];
5773 }
5774
5775 int
5776 OKToStartUserMove(x, y)
5777      int x, y;
5778 {
5779     ChessSquare from_piece;
5780     int white_piece;
5781
5782     if (matchMode) return FALSE;
5783     if (gameMode == EditPosition) return TRUE;
5784
5785     if (x >= 0 && y >= 0)
5786       from_piece = boards[currentMove][y][x];
5787     else
5788       from_piece = EmptySquare;
5789
5790     if (from_piece == EmptySquare) return FALSE;
5791
5792     white_piece = (int)from_piece >= (int)WhitePawn &&
5793       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5794
5795     switch (gameMode) {
5796       case PlayFromGameFile:
5797       case AnalyzeFile:
5798       case TwoMachinesPlay:
5799       case EndOfGame:
5800         return FALSE;
5801
5802       case IcsObserving:
5803       case IcsIdle:
5804         return FALSE;
5805
5806       case MachinePlaysWhite:
5807       case IcsPlayingBlack:
5808         if (appData.zippyPlay) return FALSE;
5809         if (white_piece) {
5810             DisplayMoveError(_("You are playing Black"));
5811             return FALSE;
5812         }
5813         break;
5814
5815       case MachinePlaysBlack:
5816       case IcsPlayingWhite:
5817         if (appData.zippyPlay) return FALSE;
5818         if (!white_piece) {
5819             DisplayMoveError(_("You are playing White"));
5820             return FALSE;
5821         }
5822         break;
5823
5824       case EditGame:
5825         if (!white_piece && WhiteOnMove(currentMove)) {
5826             DisplayMoveError(_("It is White's turn"));
5827             return FALSE;
5828         }
5829         if (white_piece && !WhiteOnMove(currentMove)) {
5830             DisplayMoveError(_("It is Black's turn"));
5831             return FALSE;
5832         }
5833         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5834             /* Editing correspondence game history */
5835             /* Could disallow this or prompt for confirmation */
5836             cmailOldMove = -1;
5837         }
5838         break;
5839
5840       case BeginningOfGame:
5841         if (appData.icsActive) return FALSE;
5842         if (!appData.noChessProgram) {
5843             if (!white_piece) {
5844                 DisplayMoveError(_("You are playing White"));
5845                 return FALSE;
5846             }
5847         }
5848         break;
5849
5850       case Training:
5851         if (!white_piece && WhiteOnMove(currentMove)) {
5852             DisplayMoveError(_("It is White's turn"));
5853             return FALSE;
5854         }
5855         if (white_piece && !WhiteOnMove(currentMove)) {
5856             DisplayMoveError(_("It is Black's turn"));
5857             return FALSE;
5858         }
5859         break;
5860
5861       default:
5862       case IcsExamining:
5863         break;
5864     }
5865     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5866         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5867         && gameMode != AnalyzeFile && gameMode != Training) {
5868         DisplayMoveError(_("Displayed position is not current"));
5869         return FALSE;
5870     }
5871     return TRUE;
5872 }
5873
5874 Boolean
5875 OnlyMove(int *x, int *y, Boolean captures) {
5876     DisambiguateClosure cl;
5877     if (appData.zippyPlay) return FALSE;
5878     switch(gameMode) {
5879       case MachinePlaysBlack:
5880       case IcsPlayingWhite:
5881       case BeginningOfGame:
5882         if(!WhiteOnMove(currentMove)) return FALSE;
5883         break;
5884       case MachinePlaysWhite:
5885       case IcsPlayingBlack:
5886         if(WhiteOnMove(currentMove)) return FALSE;
5887         break;
5888       case EditGame:
5889         break;
5890       default:
5891         return FALSE;
5892     }
5893     cl.pieceIn = EmptySquare;
5894     cl.rfIn = *y;
5895     cl.ffIn = *x;
5896     cl.rtIn = -1;
5897     cl.ftIn = -1;
5898     cl.promoCharIn = NULLCHAR;
5899     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5900     if( cl.kind == NormalMove ||
5901         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5902         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5903         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5904       fromX = cl.ff;
5905       fromY = cl.rf;
5906       *x = cl.ft;
5907       *y = cl.rt;
5908       return TRUE;
5909     }
5910     if(cl.kind != ImpossibleMove) return FALSE;
5911     cl.pieceIn = EmptySquare;
5912     cl.rfIn = -1;
5913     cl.ffIn = -1;
5914     cl.rtIn = *y;
5915     cl.ftIn = *x;
5916     cl.promoCharIn = NULLCHAR;
5917     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5918     if( cl.kind == NormalMove ||
5919         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5920         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5921         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5922       fromX = cl.ff;
5923       fromY = cl.rf;
5924       *x = cl.ft;
5925       *y = cl.rt;
5926       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5927       return TRUE;
5928     }
5929     return FALSE;
5930 }
5931
5932 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5933 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5934 int lastLoadGameUseList = FALSE;
5935 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5936 ChessMove lastLoadGameStart = EndOfFile;
5937
5938 void
5939 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5940      int fromX, fromY, toX, toY;
5941      int promoChar;
5942 {
5943     ChessMove moveType;
5944     ChessSquare pdown, pup;
5945
5946     /* Check if the user is playing in turn.  This is complicated because we
5947        let the user "pick up" a piece before it is his turn.  So the piece he
5948        tried to pick up may have been captured by the time he puts it down!
5949        Therefore we use the color the user is supposed to be playing in this
5950        test, not the color of the piece that is currently on the starting
5951        square---except in EditGame mode, where the user is playing both
5952        sides; fortunately there the capture race can't happen.  (It can
5953        now happen in IcsExamining mode, but that's just too bad.  The user
5954        will get a somewhat confusing message in that case.)
5955        */
5956
5957     switch (gameMode) {
5958       case PlayFromGameFile:
5959       case AnalyzeFile:
5960       case TwoMachinesPlay:
5961       case EndOfGame:
5962       case IcsObserving:
5963       case IcsIdle:
5964         /* We switched into a game mode where moves are not accepted,
5965            perhaps while the mouse button was down. */
5966         return;
5967
5968       case MachinePlaysWhite:
5969         /* User is moving for Black */
5970         if (WhiteOnMove(currentMove)) {
5971             DisplayMoveError(_("It is White's turn"));
5972             return;
5973         }
5974         break;
5975
5976       case MachinePlaysBlack:
5977         /* User is moving for White */
5978         if (!WhiteOnMove(currentMove)) {
5979             DisplayMoveError(_("It is Black's turn"));
5980             return;
5981         }
5982         break;
5983
5984       case EditGame:
5985       case IcsExamining:
5986       case BeginningOfGame:
5987       case AnalyzeMode:
5988       case Training:
5989         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
5990         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5991             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5992             /* User is moving for Black */
5993             if (WhiteOnMove(currentMove)) {
5994                 DisplayMoveError(_("It is White's turn"));
5995                 return;
5996             }
5997         } else {
5998             /* User is moving for White */
5999             if (!WhiteOnMove(currentMove)) {
6000                 DisplayMoveError(_("It is Black's turn"));
6001                 return;
6002             }
6003         }
6004         break;
6005
6006       case IcsPlayingBlack:
6007         /* User is moving for Black */
6008         if (WhiteOnMove(currentMove)) {
6009             if (!appData.premove) {
6010                 DisplayMoveError(_("It is White's turn"));
6011             } else if (toX >= 0 && toY >= 0) {
6012                 premoveToX = toX;
6013                 premoveToY = toY;
6014                 premoveFromX = fromX;
6015                 premoveFromY = fromY;
6016                 premovePromoChar = promoChar;
6017                 gotPremove = 1;
6018                 if (appData.debugMode)
6019                     fprintf(debugFP, "Got premove: fromX %d,"
6020                             "fromY %d, toX %d, toY %d\n",
6021                             fromX, fromY, toX, toY);
6022             }
6023             return;
6024         }
6025         break;
6026
6027       case IcsPlayingWhite:
6028         /* User is moving for White */
6029         if (!WhiteOnMove(currentMove)) {
6030             if (!appData.premove) {
6031                 DisplayMoveError(_("It is Black's turn"));
6032             } else if (toX >= 0 && toY >= 0) {
6033                 premoveToX = toX;
6034                 premoveToY = toY;
6035                 premoveFromX = fromX;
6036                 premoveFromY = fromY;
6037                 premovePromoChar = promoChar;
6038                 gotPremove = 1;
6039                 if (appData.debugMode)
6040                     fprintf(debugFP, "Got premove: fromX %d,"
6041                             "fromY %d, toX %d, toY %d\n",
6042                             fromX, fromY, toX, toY);
6043             }
6044             return;
6045         }
6046         break;
6047
6048       default:
6049         break;
6050
6051       case EditPosition:
6052         /* EditPosition, empty square, or different color piece;
6053            click-click move is possible */
6054         if (toX == -2 || toY == -2) {
6055             boards[0][fromY][fromX] = EmptySquare;
6056             DrawPosition(FALSE, boards[currentMove]);
6057             return;
6058         } else if (toX >= 0 && toY >= 0) {
6059             boards[0][toY][toX] = boards[0][fromY][fromX];
6060             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6061                 if(boards[0][fromY][0] != EmptySquare) {
6062                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6063                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6064                 }
6065             } else
6066             if(fromX == BOARD_RGHT+1) {
6067                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6068                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6069                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6070                 }
6071             } else
6072             boards[0][fromY][fromX] = EmptySquare;
6073             DrawPosition(FALSE, boards[currentMove]);
6074             return;
6075         }
6076         return;
6077     }
6078
6079     if(toX < 0 || toY < 0) return;
6080     pdown = boards[currentMove][fromY][fromX];
6081     pup = boards[currentMove][toY][toX];
6082
6083     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6084     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6085          if( pup != EmptySquare ) return;
6086          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6087            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6088                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6089            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6090            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6091            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6092            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6093          fromY = DROP_RANK;
6094     }
6095
6096     /* [HGM] always test for legality, to get promotion info */
6097     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6098                                          fromY, fromX, toY, toX, promoChar);
6099     /* [HGM] but possibly ignore an IllegalMove result */
6100     if (appData.testLegality) {
6101         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6102             DisplayMoveError(_("Illegal move"));
6103             return;
6104         }
6105     }
6106
6107     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6108 }
6109
6110 /* Common tail of UserMoveEvent and DropMenuEvent */
6111 int
6112 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6113      ChessMove moveType;
6114      int fromX, fromY, toX, toY;
6115      /*char*/int promoChar;
6116 {
6117     char *bookHit = 0;
6118
6119     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6120         // [HGM] superchess: suppress promotions to non-available piece
6121         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6122         if(WhiteOnMove(currentMove)) {
6123             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6124         } else {
6125             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6126         }
6127     }
6128
6129     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6130        move type in caller when we know the move is a legal promotion */
6131     if(moveType == NormalMove && promoChar)
6132         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6133
6134     /* [HGM] <popupFix> The following if has been moved here from
6135        UserMoveEvent(). Because it seemed to belong here (why not allow
6136        piece drops in training games?), and because it can only be
6137        performed after it is known to what we promote. */
6138     if (gameMode == Training) {
6139       /* compare the move played on the board to the next move in the
6140        * game. If they match, display the move and the opponent's response.
6141        * If they don't match, display an error message.
6142        */
6143       int saveAnimate;
6144       Board testBoard;
6145       CopyBoard(testBoard, boards[currentMove]);
6146       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6147
6148       if (CompareBoards(testBoard, boards[currentMove+1])) {
6149         ForwardInner(currentMove+1);
6150
6151         /* Autoplay the opponent's response.
6152          * if appData.animate was TRUE when Training mode was entered,
6153          * the response will be animated.
6154          */
6155         saveAnimate = appData.animate;
6156         appData.animate = animateTraining;
6157         ForwardInner(currentMove+1);
6158         appData.animate = saveAnimate;
6159
6160         /* check for the end of the game */
6161         if (currentMove >= forwardMostMove) {
6162           gameMode = PlayFromGameFile;
6163           ModeHighlight();
6164           SetTrainingModeOff();
6165           DisplayInformation(_("End of game"));
6166         }
6167       } else {
6168         DisplayError(_("Incorrect move"), 0);
6169       }
6170       return 1;
6171     }
6172
6173   /* Ok, now we know that the move is good, so we can kill
6174      the previous line in Analysis Mode */
6175   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6176                                 && currentMove < forwardMostMove) {
6177     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6178     else forwardMostMove = currentMove;
6179   }
6180
6181   /* If we need the chess program but it's dead, restart it */
6182   ResurrectChessProgram();
6183
6184   /* A user move restarts a paused game*/
6185   if (pausing)
6186     PauseEvent();
6187
6188   thinkOutput[0] = NULLCHAR;
6189
6190   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6191
6192   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6193     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6194     return 1;
6195   }
6196
6197   if (gameMode == BeginningOfGame) {
6198     if (appData.noChessProgram) {
6199       gameMode = EditGame;
6200       SetGameInfo();
6201     } else {
6202       char buf[MSG_SIZ];
6203       gameMode = MachinePlaysBlack;
6204       StartClocks();
6205       SetGameInfo();
6206       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6207       DisplayTitle(buf);
6208       if (first.sendName) {
6209         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6210         SendToProgram(buf, &first);
6211       }
6212       StartClocks();
6213     }
6214     ModeHighlight();
6215   }
6216
6217   /* Relay move to ICS or chess engine */
6218   if (appData.icsActive) {
6219     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6220         gameMode == IcsExamining) {
6221       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6222         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6223         SendToICS("draw ");
6224         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6225       }
6226       // also send plain move, in case ICS does not understand atomic claims
6227       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6228       ics_user_moved = 1;
6229     }
6230   } else {
6231     if (first.sendTime && (gameMode == BeginningOfGame ||
6232                            gameMode == MachinePlaysWhite ||
6233                            gameMode == MachinePlaysBlack)) {
6234       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6235     }
6236     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6237          // [HGM] book: if program might be playing, let it use book
6238         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6239         first.maybeThinking = TRUE;
6240     } else SendMoveToProgram(forwardMostMove-1, &first);
6241     if (currentMove == cmailOldMove + 1) {
6242       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6243     }
6244   }
6245
6246   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6247
6248   switch (gameMode) {
6249   case EditGame:
6250     if(appData.testLegality)
6251     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6252     case MT_NONE:
6253     case MT_CHECK:
6254       break;
6255     case MT_CHECKMATE:
6256     case MT_STAINMATE:
6257       if (WhiteOnMove(currentMove)) {
6258         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6259       } else {
6260         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6261       }
6262       break;
6263     case MT_STALEMATE:
6264       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6265       break;
6266     }
6267     break;
6268
6269   case MachinePlaysBlack:
6270   case MachinePlaysWhite:
6271     /* disable certain menu options while machine is thinking */
6272     SetMachineThinkingEnables();
6273     break;
6274
6275   default:
6276     break;
6277   }
6278
6279   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6280
6281   if(bookHit) { // [HGM] book: simulate book reply
6282         static char bookMove[MSG_SIZ]; // a bit generous?
6283
6284         programStats.nodes = programStats.depth = programStats.time =
6285         programStats.score = programStats.got_only_move = 0;
6286         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6287
6288         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6289         strcat(bookMove, bookHit);
6290         HandleMachineMove(bookMove, &first);
6291   }
6292   return 1;
6293 }
6294
6295 void
6296 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6297      Board board;
6298      int flags;
6299      ChessMove kind;
6300      int rf, ff, rt, ft;
6301      VOIDSTAR closure;
6302 {
6303     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6304     Markers *m = (Markers *) closure;
6305     if(rf == fromY && ff == fromX)
6306         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6307                          || kind == WhiteCapturesEnPassant
6308                          || kind == BlackCapturesEnPassant);
6309     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6310 }
6311
6312 void
6313 MarkTargetSquares(int clear)
6314 {
6315   int x, y;
6316   if(!appData.markers || !appData.highlightDragging ||
6317      !appData.testLegality || gameMode == EditPosition) return;
6318   if(clear) {
6319     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6320   } else {
6321     int capt = 0;
6322     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6323     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6324       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6325       if(capt)
6326       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6327     }
6328   }
6329   DrawPosition(TRUE, NULL);
6330 }
6331
6332 int
6333 Explode(Board board, int fromX, int fromY, int toX, int toY)
6334 {
6335     if(gameInfo.variant == VariantAtomic &&
6336        (board[toY][toX] != EmptySquare ||                     // capture?
6337         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6338                          board[fromY][fromX] == BlackPawn   )
6339       )) {
6340         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6341         return TRUE;
6342     }
6343     return FALSE;
6344 }
6345
6346 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6347
6348 void LeftClick(ClickType clickType, int xPix, int yPix)
6349 {
6350     int x, y;
6351     Boolean saveAnimate;
6352     static int second = 0, promotionChoice = 0, dragging = 0;
6353     char promoChoice = NULLCHAR;
6354
6355     if(appData.seekGraph && appData.icsActive && loggedOn &&
6356         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6357         SeekGraphClick(clickType, xPix, yPix, 0);
6358         return;
6359     }
6360
6361     if (clickType == Press) ErrorPopDown();
6362     MarkTargetSquares(1);
6363
6364     x = EventToSquare(xPix, BOARD_WIDTH);
6365     y = EventToSquare(yPix, BOARD_HEIGHT);
6366     if (!flipView && y >= 0) {
6367         y = BOARD_HEIGHT - 1 - y;
6368     }
6369     if (flipView && x >= 0) {
6370         x = BOARD_WIDTH - 1 - x;
6371     }
6372
6373     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6374         if(clickType == Release) return; // ignore upclick of click-click destination
6375         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6376         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6377         if(gameInfo.holdingsWidth &&
6378                 (WhiteOnMove(currentMove)
6379                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6380                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6381             // click in right holdings, for determining promotion piece
6382             ChessSquare p = boards[currentMove][y][x];
6383             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6384             if(p != EmptySquare) {
6385                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6386                 fromX = fromY = -1;
6387                 return;
6388             }
6389         }
6390         DrawPosition(FALSE, boards[currentMove]);
6391         return;
6392     }
6393
6394     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6395     if(clickType == Press
6396             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6397               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6398               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6399         return;
6400
6401     autoQueen = appData.alwaysPromoteToQueen;
6402
6403     if (fromX == -1) {
6404       gatingPiece = EmptySquare;
6405       if (clickType != Press) {
6406         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6407             DragPieceEnd(xPix, yPix); dragging = 0;
6408             DrawPosition(FALSE, NULL);
6409         }
6410         return;
6411       }
6412       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6413             /* First square */
6414             if (OKToStartUserMove(x, y)) {
6415                 fromX = x;
6416                 fromY = y;
6417                 second = 0;
6418                 MarkTargetSquares(0);
6419                 DragPieceBegin(xPix, yPix); dragging = 1;
6420                 if (appData.highlightDragging) {
6421                     SetHighlights(x, y, -1, -1);
6422                 }
6423             }
6424             return;
6425         }
6426     }
6427
6428     /* fromX != -1 */
6429     if (clickType == Press && gameMode != EditPosition) {
6430         ChessSquare fromP;
6431         ChessSquare toP;
6432         int frc;
6433
6434         // ignore off-board to clicks
6435         if(y < 0 || x < 0) return;
6436
6437         /* Check if clicking again on the same color piece */
6438         fromP = boards[currentMove][fromY][fromX];
6439         toP = boards[currentMove][y][x];
6440         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6441         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6442              WhitePawn <= toP && toP <= WhiteKing &&
6443              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6444              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6445             (BlackPawn <= fromP && fromP <= BlackKing &&
6446              BlackPawn <= toP && toP <= BlackKing &&
6447              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6448              !(fromP == BlackKing && toP == BlackRook && frc))) {
6449             /* Clicked again on same color piece -- changed his mind */
6450             second = (x == fromX && y == fromY);
6451            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6452             if (appData.highlightDragging) {
6453                 SetHighlights(x, y, -1, -1);
6454             } else {
6455                 ClearHighlights();
6456             }
6457             if (OKToStartUserMove(x, y)) {
6458                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6459                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6460                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6461                  gatingPiece = boards[currentMove][fromY][fromX];
6462                 else gatingPiece = EmptySquare;
6463                 fromX = x;
6464                 fromY = y; dragging = 1;
6465                 MarkTargetSquares(0);
6466                 DragPieceBegin(xPix, yPix);
6467             }
6468            }
6469            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6470            second = FALSE; 
6471         }
6472         // ignore clicks on holdings
6473         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6474     }
6475
6476     if (clickType == Release && x == fromX && y == fromY) {
6477         DragPieceEnd(xPix, yPix); dragging = 0;
6478         if (appData.animateDragging) {
6479             /* Undo animation damage if any */
6480             DrawPosition(FALSE, NULL);
6481         }
6482         if (second) {
6483             /* Second up/down in same square; just abort move */
6484             second = 0;
6485             fromX = fromY = -1;
6486             gatingPiece = EmptySquare;
6487             ClearHighlights();
6488             gotPremove = 0;
6489             ClearPremoveHighlights();
6490         } else {
6491             /* First upclick in same square; start click-click mode */
6492             SetHighlights(x, y, -1, -1);
6493         }
6494         return;
6495     }
6496
6497     /* we now have a different from- and (possibly off-board) to-square */
6498     /* Completed move */
6499     toX = x;
6500     toY = y;
6501     saveAnimate = appData.animate;
6502     if (clickType == Press) {
6503         /* Finish clickclick move */
6504         if (appData.animate || appData.highlightLastMove) {
6505             SetHighlights(fromX, fromY, toX, toY);
6506         } else {
6507             ClearHighlights();
6508         }
6509     } else {
6510         /* Finish drag move */
6511         if (appData.highlightLastMove) {
6512             SetHighlights(fromX, fromY, toX, toY);
6513         } else {
6514             ClearHighlights();
6515         }
6516         DragPieceEnd(xPix, yPix); dragging = 0;
6517         /* Don't animate move and drag both */
6518         appData.animate = FALSE;
6519     }
6520
6521     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6522     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6523         ChessSquare piece = boards[currentMove][fromY][fromX];
6524         if(gameMode == EditPosition && piece != EmptySquare &&
6525            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6526             int n;
6527
6528             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6529                 n = PieceToNumber(piece - (int)BlackPawn);
6530                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6531                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6532                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6533             } else
6534             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6535                 n = PieceToNumber(piece);
6536                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6537                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6538                 boards[currentMove][n][BOARD_WIDTH-2]++;
6539             }
6540             boards[currentMove][fromY][fromX] = EmptySquare;
6541         }
6542         ClearHighlights();
6543         fromX = fromY = -1;
6544         DrawPosition(TRUE, boards[currentMove]);
6545         return;
6546     }
6547
6548     // off-board moves should not be highlighted
6549     if(x < 0 || y < 0) ClearHighlights();
6550
6551     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6552
6553     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6554         SetHighlights(fromX, fromY, toX, toY);
6555         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6556             // [HGM] super: promotion to captured piece selected from holdings
6557             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6558             promotionChoice = TRUE;
6559             // kludge follows to temporarily execute move on display, without promoting yet
6560             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6561             boards[currentMove][toY][toX] = p;
6562             DrawPosition(FALSE, boards[currentMove]);
6563             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6564             boards[currentMove][toY][toX] = q;
6565             DisplayMessage("Click in holdings to choose piece", "");
6566             return;
6567         }
6568         PromotionPopUp();
6569     } else {
6570         int oldMove = currentMove;
6571         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6572         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6573         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6574         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6575            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6576             DrawPosition(TRUE, boards[currentMove]);
6577         fromX = fromY = -1;
6578     }
6579     appData.animate = saveAnimate;
6580     if (appData.animate || appData.animateDragging) {
6581         /* Undo animation damage if needed */
6582         DrawPosition(FALSE, NULL);
6583     }
6584 }
6585
6586 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6587 {   // front-end-free part taken out of PieceMenuPopup
6588     int whichMenu; int xSqr, ySqr;
6589
6590     if(seekGraphUp) { // [HGM] seekgraph
6591         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6592         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6593         return -2;
6594     }
6595
6596     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6597          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6598         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6599         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6600         if(action == Press)   {
6601             originalFlip = flipView;
6602             flipView = !flipView; // temporarily flip board to see game from partners perspective
6603             DrawPosition(TRUE, partnerBoard);
6604             DisplayMessage(partnerStatus, "");
6605             partnerUp = TRUE;
6606         } else if(action == Release) {
6607             flipView = originalFlip;
6608             DrawPosition(TRUE, boards[currentMove]);
6609             partnerUp = FALSE;
6610         }
6611         return -2;
6612     }
6613
6614     xSqr = EventToSquare(x, BOARD_WIDTH);
6615     ySqr = EventToSquare(y, BOARD_HEIGHT);
6616     if (action == Release) UnLoadPV(); // [HGM] pv
6617     if (action != Press) return -2; // return code to be ignored
6618     switch (gameMode) {
6619       case IcsExamining:
6620         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6621       case EditPosition:
6622         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6623         if (xSqr < 0 || ySqr < 0) return -1;\r
6624         whichMenu = 0; // edit-position menu
6625         break;
6626       case IcsObserving:
6627         if(!appData.icsEngineAnalyze) return -1;
6628       case IcsPlayingWhite:
6629       case IcsPlayingBlack:
6630         if(!appData.zippyPlay) goto noZip;
6631       case AnalyzeMode:
6632       case AnalyzeFile:
6633       case MachinePlaysWhite:
6634       case MachinePlaysBlack:
6635       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6636         if (!appData.dropMenu) {
6637           LoadPV(x, y);
6638           return 2; // flag front-end to grab mouse events
6639         }
6640         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6641            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6642       case EditGame:
6643       noZip:
6644         if (xSqr < 0 || ySqr < 0) return -1;
6645         if (!appData.dropMenu || appData.testLegality &&
6646             gameInfo.variant != VariantBughouse &&
6647             gameInfo.variant != VariantCrazyhouse) return -1;
6648         whichMenu = 1; // drop menu
6649         break;
6650       default:
6651         return -1;
6652     }
6653
6654     if (((*fromX = xSqr) < 0) ||
6655         ((*fromY = ySqr) < 0)) {
6656         *fromX = *fromY = -1;
6657         return -1;
6658     }
6659     if (flipView)
6660       *fromX = BOARD_WIDTH - 1 - *fromX;
6661     else
6662       *fromY = BOARD_HEIGHT - 1 - *fromY;
6663
6664     return whichMenu;
6665 }
6666
6667 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6668 {
6669 //    char * hint = lastHint;
6670     FrontEndProgramStats stats;
6671
6672     stats.which = cps == &first ? 0 : 1;
6673     stats.depth = cpstats->depth;
6674     stats.nodes = cpstats->nodes;
6675     stats.score = cpstats->score;
6676     stats.time = cpstats->time;
6677     stats.pv = cpstats->movelist;
6678     stats.hint = lastHint;
6679     stats.an_move_index = 0;
6680     stats.an_move_count = 0;
6681
6682     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6683         stats.hint = cpstats->move_name;
6684         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6685         stats.an_move_count = cpstats->nr_moves;
6686     }
6687
6688     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
6689
6690     SetProgramStats( &stats );
6691 }
6692
6693 void
6694 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6695 {       // count all piece types
6696         int p, f, r;
6697         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6698         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6699         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6700                 p = board[r][f];
6701                 pCnt[p]++;
6702                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6703                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6704                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6705                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6706                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6707                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6708         }
6709 }
6710
6711 int
6712 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6713 {
6714         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6715         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6716
6717         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6718         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6719         if(myPawns == 2 && nMine == 3) // KPP
6720             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6721         if(myPawns == 1 && nMine == 2) // KP
6722             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6723         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6724             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6725         if(myPawns) return FALSE;
6726         if(pCnt[WhiteRook+side])
6727             return pCnt[BlackRook-side] ||
6728                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6729                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6730                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6731         if(pCnt[WhiteCannon+side]) {
6732             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6733             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6734         }
6735         if(pCnt[WhiteKnight+side])
6736             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6737         return FALSE;
6738 }
6739
6740 int
6741 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6742 {
6743         VariantClass v = gameInfo.variant;
6744
6745         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6746         if(v == VariantShatranj) return TRUE; // always winnable through baring
6747         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6748         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6749
6750         if(v == VariantXiangqi) {
6751                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6752
6753                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6754                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6755                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6756                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6757                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6758                 if(stale) // we have at least one last-rank P plus perhaps C
6759                     return majors // KPKX
6760                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6761                 else // KCA*E*
6762                     return pCnt[WhiteFerz+side] // KCAK
6763                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6764                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6765                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6766
6767         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6768                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6769
6770                 if(nMine == 1) return FALSE; // bare King
6771                 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
6772                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6773                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6774                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6775                 if(pCnt[WhiteKnight+side])
6776                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6777                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6778                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6779                 if(nBishops)
6780                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6781                 if(pCnt[WhiteAlfil+side])
6782                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6783                 if(pCnt[WhiteWazir+side])
6784                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6785         }
6786
6787         return TRUE;
6788 }
6789
6790 int
6791 Adjudicate(ChessProgramState *cps)
6792 {       // [HGM] some adjudications useful with buggy engines
6793         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6794         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6795         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6796         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6797         int k, count = 0; static int bare = 1;
6798         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6799         Boolean canAdjudicate = !appData.icsActive;
6800
6801         // most tests only when we understand the game, i.e. legality-checking on
6802             if( appData.testLegality )
6803             {   /* [HGM] Some more adjudications for obstinate engines */
6804                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6805                 static int moveCount = 6;
6806                 ChessMove result;
6807                 char *reason = NULL;
6808
6809                 /* Count what is on board. */
6810                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6811
6812                 /* Some material-based adjudications that have to be made before stalemate test */
6813                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6814                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6815                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6816                      if(canAdjudicate && appData.checkMates) {
6817                          if(engineOpponent)
6818                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6819                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6820                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6821                          return 1;
6822                      }
6823                 }
6824
6825                 /* Bare King in Shatranj (loses) or Losers (wins) */
6826                 if( nrW == 1 || nrB == 1) {
6827                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6828                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6829                      if(canAdjudicate && appData.checkMates) {
6830                          if(engineOpponent)
6831                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6832                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6833                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6834                          return 1;
6835                      }
6836                   } else
6837                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6838                   {    /* bare King */
6839                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6840                         if(canAdjudicate && appData.checkMates) {
6841                             /* but only adjudicate if adjudication enabled */
6842                             if(engineOpponent)
6843                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6844                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6845                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6846                             return 1;
6847                         }
6848                   }
6849                 } else bare = 1;
6850
6851
6852             // don't wait for engine to announce game end if we can judge ourselves
6853             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6854               case MT_CHECK:
6855                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6856                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6857                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6858                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6859                             checkCnt++;
6860                         if(checkCnt >= 2) {
6861                             reason = "Xboard adjudication: 3rd check";
6862                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6863                             break;
6864                         }
6865                     }
6866                 }
6867               case MT_NONE:
6868               default:
6869                 break;
6870               case MT_STALEMATE:
6871               case MT_STAINMATE:
6872                 reason = "Xboard adjudication: Stalemate";
6873                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6874                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6875                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6876                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6877                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6878                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6879                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6880                                                                         EP_CHECKMATE : EP_WINS);
6881                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6882                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6883                 }
6884                 break;
6885               case MT_CHECKMATE:
6886                 reason = "Xboard adjudication: Checkmate";
6887                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6888                 break;
6889             }
6890
6891                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6892                     case EP_STALEMATE:
6893                         result = GameIsDrawn; break;
6894                     case EP_CHECKMATE:
6895                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6896                     case EP_WINS:
6897                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6898                     default:
6899                         result = EndOfFile;
6900                 }
6901                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6902                     if(engineOpponent)
6903                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
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                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6923                          return 1;
6924                      }
6925                 }
6926
6927                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6928                 if(gameInfo.variant == VariantXiangqi ?
6929                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6930                  : nrW + nrB == 4 &&
6931                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6932                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6933                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6934                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6935                    ) ) {
6936                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6937                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6938                           if(engineOpponent) {
6939                             SendToProgram("force\n", engineOpponent); // suppress reply
6940                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6941                           }
6942                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6943                           return 1;
6944                      }
6945                 } else moveCount = 6;
6946             }
6947         if (appData.debugMode) { int i;
6948             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6949                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6950                     appData.drawRepeats);
6951             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6952               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6953
6954         }
6955
6956         // Repetition draws and 50-move rule can be applied independently of legality testing
6957
6958                 /* Check for rep-draws */
6959                 count = 0;
6960                 for(k = forwardMostMove-2;
6961                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6962                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6963                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6964                     k-=2)
6965                 {   int rights=0;
6966                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6967                         /* compare castling rights */
6968                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6969                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6970                                 rights++; /* King lost rights, while rook still had them */
6971                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6972                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6973                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6974                                    rights++; /* but at least one rook lost them */
6975                         }
6976                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6977                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6978                                 rights++;
6979                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6980                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6981                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6982                                    rights++;
6983                         }
6984                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6985                             && appData.drawRepeats > 1) {
6986                              /* adjudicate after user-specified nr of repeats */
6987                              int result = GameIsDrawn;
6988                              char *details = "XBoard adjudication: repetition draw";
6989                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6990                                 // [HGM] xiangqi: check for forbidden perpetuals
6991                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6992                                 for(m=forwardMostMove; m>k; m-=2) {
6993                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6994                                         ourPerpetual = 0; // the current mover did not always check
6995                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6996                                         hisPerpetual = 0; // the opponent did not always check
6997                                 }
6998                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6999                                                                         ourPerpetual, hisPerpetual);
7000                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7001                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7002                                     details = "Xboard adjudication: perpetual checking";
7003                                 } else
7004                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7005                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7006                                 } else
7007                                 // Now check for perpetual chases
7008                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7009                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7010                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7011                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7012                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7013                                         details = "Xboard adjudication: perpetual chasing";
7014                                     } else
7015                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7016                                         break; // Abort repetition-checking loop.
7017                                 }
7018                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7019                              }
7020                              if(engineOpponent) {
7021                                SendToProgram("force\n", engineOpponent); // suppress reply
7022                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7023                              }
7024                              GameEnds( result, details, GE_XBOARD );
7025                              return 1;
7026                         }
7027                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7028                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7029                     }
7030                 }
7031
7032                 /* Now we test for 50-move draws. Determine ply count */
7033                 count = forwardMostMove;
7034                 /* look for last irreversble move */
7035                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7036                     count--;
7037                 /* if we hit starting position, add initial plies */
7038                 if( count == backwardMostMove )
7039                     count -= initialRulePlies;
7040                 count = forwardMostMove - count;
7041                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7042                         // adjust reversible move counter for checks in Xiangqi
7043                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7044                         if(i < backwardMostMove) i = backwardMostMove;
7045                         while(i <= forwardMostMove) {
7046                                 lastCheck = inCheck; // check evasion does not count
7047                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7048                                 if(inCheck || lastCheck) count--; // check does not count
7049                                 i++;
7050                         }
7051                 }
7052                 if( count >= 100)
7053                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7054                          /* this is used to judge if draw claims are legal */
7055                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7056                          if(engineOpponent) {
7057                            SendToProgram("force\n", engineOpponent); // suppress reply
7058                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7059                          }
7060                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7061                          return 1;
7062                 }
7063
7064                 /* if draw offer is pending, treat it as a draw claim
7065                  * when draw condition present, to allow engines a way to
7066                  * claim draws before making their move to avoid a race
7067                  * condition occurring after their move
7068                  */
7069                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7070                          char *p = NULL;
7071                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7072                              p = "Draw claim: 50-move rule";
7073                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7074                              p = "Draw claim: 3-fold repetition";
7075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7076                              p = "Draw claim: insufficient mating material";
7077                          if( p != NULL && canAdjudicate) {
7078                              if(engineOpponent) {
7079                                SendToProgram("force\n", engineOpponent); // suppress reply
7080                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7081                              }
7082                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7083                              return 1;
7084                          }
7085                 }
7086
7087                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7088                     if(engineOpponent) {
7089                       SendToProgram("force\n", engineOpponent); // suppress reply
7090                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7091                     }
7092                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7093                     return 1;
7094                 }
7095         return 0;
7096 }
7097
7098 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7099 {   // [HGM] book: this routine intercepts moves to simulate book replies
7100     char *bookHit = NULL;
7101
7102     //first determine if the incoming move brings opponent into his book
7103     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7104         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7105     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7106     if(bookHit != NULL && !cps->bookSuspend) {
7107         // make sure opponent is not going to reply after receiving move to book position
7108         SendToProgram("force\n", cps);
7109         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7110     }
7111     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7112     // now arrange restart after book miss
7113     if(bookHit) {
7114         // after a book hit we never send 'go', and the code after the call to this routine
7115         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7116         char buf[MSG_SIZ];
7117         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7118         SendToProgram(buf, cps);
7119         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7120     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7121         SendToProgram("go\n", cps);
7122         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7123     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7124         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7125             SendToProgram("go\n", cps);
7126         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7127     }
7128     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7129 }
7130
7131 char *savedMessage;
7132 ChessProgramState *savedState;
7133 void DeferredBookMove(void)
7134 {
7135         if(savedState->lastPing != savedState->lastPong)
7136                     ScheduleDelayedEvent(DeferredBookMove, 10);
7137         else
7138         HandleMachineMove(savedMessage, savedState);
7139 }
7140
7141 void
7142 HandleMachineMove(message, cps)
7143      char *message;
7144      ChessProgramState *cps;
7145 {
7146     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7147     char realname[MSG_SIZ];
7148     int fromX, fromY, toX, toY;
7149     ChessMove moveType;
7150     char promoChar;
7151     char *p;
7152     int machineWhite;
7153     char *bookHit;
7154
7155     cps->userError = 0;
7156
7157 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7158     /*
7159      * Kludge to ignore BEL characters
7160      */
7161     while (*message == '\007') message++;
7162
7163     /*
7164      * [HGM] engine debug message: ignore lines starting with '#' character
7165      */
7166     if(cps->debug && *message == '#') return;
7167
7168     /*
7169      * Look for book output
7170      */
7171     if (cps == &first && bookRequested) {
7172         if (message[0] == '\t' || message[0] == ' ') {
7173             /* Part of the book output is here; append it */
7174             strcat(bookOutput, message);
7175             strcat(bookOutput, "  \n");
7176             return;
7177         } else if (bookOutput[0] != NULLCHAR) {
7178             /* All of book output has arrived; display it */
7179             char *p = bookOutput;
7180             while (*p != NULLCHAR) {
7181                 if (*p == '\t') *p = ' ';
7182                 p++;
7183             }
7184             DisplayInformation(bookOutput);
7185             bookRequested = FALSE;
7186             /* Fall through to parse the current output */
7187         }
7188     }
7189
7190     /*
7191      * Look for machine move.
7192      */
7193     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7194         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7195     {
7196         /* This method is only useful on engines that support ping */
7197         if (cps->lastPing != cps->lastPong) {
7198           if (gameMode == BeginningOfGame) {
7199             /* Extra move from before last new; ignore */
7200             if (appData.debugMode) {
7201                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7202             }
7203           } else {
7204             if (appData.debugMode) {
7205                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7206                         cps->which, gameMode);
7207             }
7208
7209             SendToProgram("undo\n", cps);
7210           }
7211           return;
7212         }
7213
7214         switch (gameMode) {
7215           case BeginningOfGame:
7216             /* Extra move from before last reset; ignore */
7217             if (appData.debugMode) {
7218                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7219             }
7220             return;
7221
7222           case EndOfGame:
7223           case IcsIdle:
7224           default:
7225             /* Extra move after we tried to stop.  The mode test is
7226                not a reliable way of detecting this problem, but it's
7227                the best we can do on engines that don't support ping.
7228             */
7229             if (appData.debugMode) {
7230                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7231                         cps->which, gameMode);
7232             }
7233             SendToProgram("undo\n", cps);
7234             return;
7235
7236           case MachinePlaysWhite:
7237           case IcsPlayingWhite:
7238             machineWhite = TRUE;
7239             break;
7240
7241           case MachinePlaysBlack:
7242           case IcsPlayingBlack:
7243             machineWhite = FALSE;
7244             break;
7245
7246           case TwoMachinesPlay:
7247             machineWhite = (cps->twoMachinesColor[0] == 'w');
7248             break;
7249         }
7250         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7251             if (appData.debugMode) {
7252                 fprintf(debugFP,
7253                         "Ignoring move out of turn by %s, gameMode %d"
7254                         ", forwardMost %d\n",
7255                         cps->which, gameMode, forwardMostMove);
7256             }
7257             return;
7258         }
7259
7260     if (appData.debugMode) { int f = forwardMostMove;
7261         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7262                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7263                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7264     }
7265         if(cps->alphaRank) AlphaRank(machineMove, 4);
7266         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7267                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7268             /* Machine move could not be parsed; ignore it. */
7269           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7270                     machineMove, _(cps->which));
7271             DisplayError(buf1, 0);
7272             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7273                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7274             if (gameMode == TwoMachinesPlay) {
7275               GameEnds(machineWhite ? BlackWins : WhiteWins,
7276                        buf1, GE_XBOARD);
7277             }
7278             return;
7279         }
7280
7281         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7282         /* So we have to redo legality test with true e.p. status here,  */
7283         /* to make sure an illegal e.p. capture does not slip through,   */
7284         /* to cause a forfeit on a justified illegal-move complaint      */
7285         /* of the opponent.                                              */
7286         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7287            ChessMove moveType;
7288            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7289                              fromY, fromX, toY, toX, promoChar);
7290             if (appData.debugMode) {
7291                 int i;
7292                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7293                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7294                 fprintf(debugFP, "castling rights\n");
7295             }
7296             if(moveType == IllegalMove) {
7297               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7298                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7299                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7300                            buf1, GE_XBOARD);
7301                 return;
7302            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7303            /* [HGM] Kludge to handle engines that send FRC-style castling
7304               when they shouldn't (like TSCP-Gothic) */
7305            switch(moveType) {
7306              case WhiteASideCastleFR:
7307              case BlackASideCastleFR:
7308                toX+=2;
7309                currentMoveString[2]++;
7310                break;
7311              case WhiteHSideCastleFR:
7312              case BlackHSideCastleFR:
7313                toX--;
7314                currentMoveString[2]--;
7315                break;
7316              default: ; // nothing to do, but suppresses warning of pedantic compilers
7317            }
7318         }
7319         hintRequested = FALSE;
7320         lastHint[0] = NULLCHAR;
7321         bookRequested = FALSE;
7322         /* Program may be pondering now */
7323         cps->maybeThinking = TRUE;
7324         if (cps->sendTime == 2) cps->sendTime = 1;
7325         if (cps->offeredDraw) cps->offeredDraw--;
7326
7327         /* [AS] Save move info*/
7328         pvInfoList[ forwardMostMove ].score = programStats.score;
7329         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7330         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7331
7332         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7333
7334         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7335         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7336             int count = 0;
7337
7338             while( count < adjudicateLossPlies ) {
7339                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7340
7341                 if( count & 1 ) {
7342                     score = -score; /* Flip score for winning side */
7343                 }
7344
7345                 if( score > adjudicateLossThreshold ) {
7346                     break;
7347                 }
7348
7349                 count++;
7350             }
7351
7352             if( count >= adjudicateLossPlies ) {
7353                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7354
7355                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7356                     "Xboard adjudication",
7357                     GE_XBOARD );
7358
7359                 return;
7360             }
7361         }
7362
7363         if(Adjudicate(cps)) {
7364             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7365             return; // [HGM] adjudicate: for all automatic game ends
7366         }
7367
7368 #if ZIPPY
7369         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7370             first.initDone) {
7371           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7372                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7373                 SendToICS("draw ");
7374                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7375           }
7376           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7377           ics_user_moved = 1;
7378           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7379                 char buf[3*MSG_SIZ];
7380
7381                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7382                         programStats.score / 100.,
7383                         programStats.depth,
7384                         programStats.time / 100.,
7385                         (unsigned int)programStats.nodes,
7386                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7387                         programStats.movelist);
7388                 SendToICS(buf);
7389 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7390           }
7391         }
7392 #endif
7393
7394         /* [AS] Clear stats for next move */
7395         ClearProgramStats();
7396         thinkOutput[0] = NULLCHAR;
7397         hiddenThinkOutputState = 0;
7398
7399         bookHit = NULL;
7400         if (gameMode == TwoMachinesPlay) {
7401             /* [HGM] relaying draw offers moved to after reception of move */
7402             /* and interpreting offer as claim if it brings draw condition */
7403             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7404                 SendToProgram("draw\n", cps->other);
7405             }
7406             if (cps->other->sendTime) {
7407                 SendTimeRemaining(cps->other,
7408                                   cps->other->twoMachinesColor[0] == 'w');
7409             }
7410             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7411             if (firstMove && !bookHit) {
7412                 firstMove = FALSE;
7413                 if (cps->other->useColors) {
7414                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7415                 }
7416                 SendToProgram("go\n", cps->other);
7417             }
7418             cps->other->maybeThinking = TRUE;
7419         }
7420
7421         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7422
7423         if (!pausing && appData.ringBellAfterMoves) {
7424             RingBell();
7425         }
7426
7427         /*
7428          * Reenable menu items that were disabled while
7429          * machine was thinking
7430          */
7431         if (gameMode != TwoMachinesPlay)
7432             SetUserThinkingEnables();
7433
7434         // [HGM] book: after book hit opponent has received move and is now in force mode
7435         // force the book reply into it, and then fake that it outputted this move by jumping
7436         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7437         if(bookHit) {
7438                 static char bookMove[MSG_SIZ]; // a bit generous?
7439
7440                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7441                 strcat(bookMove, bookHit);
7442                 message = bookMove;
7443                 cps = cps->other;
7444                 programStats.nodes = programStats.depth = programStats.time =
7445                 programStats.score = programStats.got_only_move = 0;
7446                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7447
7448                 if(cps->lastPing != cps->lastPong) {
7449                     savedMessage = message; // args for deferred call
7450                     savedState = cps;
7451                     ScheduleDelayedEvent(DeferredBookMove, 10);
7452                     return;
7453                 }
7454                 goto FakeBookMove;
7455         }
7456
7457         return;
7458     }
7459
7460     /* Set special modes for chess engines.  Later something general
7461      *  could be added here; for now there is just one kludge feature,
7462      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7463      *  when "xboard" is given as an interactive command.
7464      */
7465     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7466         cps->useSigint = FALSE;
7467         cps->useSigterm = FALSE;
7468     }
7469     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7470       ParseFeatures(message+8, cps);
7471       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7472     }
7473
7474     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7475       int dummy, s=6; char buf[MSG_SIZ];
7476       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7477       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7478       ParseFEN(boards[0], &dummy, message+s);
7479       DrawPosition(TRUE, boards[0]);
7480       startedFromSetupPosition = TRUE;
7481       return;
7482     }
7483     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7484      * want this, I was asked to put it in, and obliged.
7485      */
7486     if (!strncmp(message, "setboard ", 9)) {
7487         Board initial_position;
7488
7489         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7490
7491         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7492             DisplayError(_("Bad FEN received from engine"), 0);
7493             return ;
7494         } else {
7495            Reset(TRUE, FALSE);
7496            CopyBoard(boards[0], initial_position);
7497            initialRulePlies = FENrulePlies;
7498            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7499            else gameMode = MachinePlaysBlack;
7500            DrawPosition(FALSE, boards[currentMove]);
7501         }
7502         return;
7503     }
7504
7505     /*
7506      * Look for communication commands
7507      */
7508     if (!strncmp(message, "telluser ", 9)) {
7509         if(message[9] == '\\' && message[10] == '\\')
7510             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7511         DisplayNote(message + 9);
7512         return;
7513     }
7514     if (!strncmp(message, "tellusererror ", 14)) {
7515         cps->userError = 1;
7516         if(message[14] == '\\' && message[15] == '\\')
7517             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7518         DisplayError(message + 14, 0);
7519         return;
7520     }
7521     if (!strncmp(message, "tellopponent ", 13)) {
7522       if (appData.icsActive) {
7523         if (loggedOn) {
7524           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7525           SendToICS(buf1);
7526         }
7527       } else {
7528         DisplayNote(message + 13);
7529       }
7530       return;
7531     }
7532     if (!strncmp(message, "tellothers ", 11)) {
7533       if (appData.icsActive) {
7534         if (loggedOn) {
7535           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7536           SendToICS(buf1);
7537         }
7538       }
7539       return;
7540     }
7541     if (!strncmp(message, "tellall ", 8)) {
7542       if (appData.icsActive) {
7543         if (loggedOn) {
7544           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7545           SendToICS(buf1);
7546         }
7547       } else {
7548         DisplayNote(message + 8);
7549       }
7550       return;
7551     }
7552     if (strncmp(message, "warning", 7) == 0) {
7553         /* Undocumented feature, use tellusererror in new code */
7554         DisplayError(message, 0);
7555         return;
7556     }
7557     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7558         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7559         strcat(realname, " query");
7560         AskQuestion(realname, buf2, buf1, cps->pr);
7561         return;
7562     }
7563     /* Commands from the engine directly to ICS.  We don't allow these to be
7564      *  sent until we are logged on. Crafty kibitzes have been known to
7565      *  interfere with the login process.
7566      */
7567     if (loggedOn) {
7568         if (!strncmp(message, "tellics ", 8)) {
7569             SendToICS(message + 8);
7570             SendToICS("\n");
7571             return;
7572         }
7573         if (!strncmp(message, "tellicsnoalias ", 15)) {
7574             SendToICS(ics_prefix);
7575             SendToICS(message + 15);
7576             SendToICS("\n");
7577             return;
7578         }
7579         /* The following are for backward compatibility only */
7580         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7581             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7582             SendToICS(ics_prefix);
7583             SendToICS(message);
7584             SendToICS("\n");
7585             return;
7586         }
7587     }
7588     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7589         return;
7590     }
7591     /*
7592      * If the move is illegal, cancel it and redraw the board.
7593      * Also deal with other error cases.  Matching is rather loose
7594      * here to accommodate engines written before the spec.
7595      */
7596     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7597         strncmp(message, "Error", 5) == 0) {
7598         if (StrStr(message, "name") ||
7599             StrStr(message, "rating") || StrStr(message, "?") ||
7600             StrStr(message, "result") || StrStr(message, "board") ||
7601             StrStr(message, "bk") || StrStr(message, "computer") ||
7602             StrStr(message, "variant") || StrStr(message, "hint") ||
7603             StrStr(message, "random") || StrStr(message, "depth") ||
7604             StrStr(message, "accepted")) {
7605             return;
7606         }
7607         if (StrStr(message, "protover")) {
7608           /* Program is responding to input, so it's apparently done
7609              initializing, and this error message indicates it is
7610              protocol version 1.  So we don't need to wait any longer
7611              for it to initialize and send feature commands. */
7612           FeatureDone(cps, 1);
7613           cps->protocolVersion = 1;
7614           return;
7615         }
7616         cps->maybeThinking = FALSE;
7617
7618         if (StrStr(message, "draw")) {
7619             /* Program doesn't have "draw" command */
7620             cps->sendDrawOffers = 0;
7621             return;
7622         }
7623         if (cps->sendTime != 1 &&
7624             (StrStr(message, "time") || StrStr(message, "otim"))) {
7625           /* Program apparently doesn't have "time" or "otim" command */
7626           cps->sendTime = 0;
7627           return;
7628         }
7629         if (StrStr(message, "analyze")) {
7630             cps->analysisSupport = FALSE;
7631             cps->analyzing = FALSE;
7632             Reset(FALSE, TRUE);
7633             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7634             DisplayError(buf2, 0);
7635             return;
7636         }
7637         if (StrStr(message, "(no matching move)st")) {
7638           /* Special kludge for GNU Chess 4 only */
7639           cps->stKludge = TRUE;
7640           SendTimeControl(cps, movesPerSession, timeControl,
7641                           timeIncrement, appData.searchDepth,
7642                           searchTime);
7643           return;
7644         }
7645         if (StrStr(message, "(no matching move)sd")) {
7646           /* Special kludge for GNU Chess 4 only */
7647           cps->sdKludge = TRUE;
7648           SendTimeControl(cps, movesPerSession, timeControl,
7649                           timeIncrement, appData.searchDepth,
7650                           searchTime);
7651           return;
7652         }
7653         if (!StrStr(message, "llegal")) {
7654             return;
7655         }
7656         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7657             gameMode == IcsIdle) return;
7658         if (forwardMostMove <= backwardMostMove) return;
7659         if (pausing) PauseEvent();
7660       if(appData.forceIllegal) {
7661             // [HGM] illegal: machine refused move; force position after move into it
7662           SendToProgram("force\n", cps);
7663           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7664                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7665                 // when black is to move, while there might be nothing on a2 or black
7666                 // might already have the move. So send the board as if white has the move.
7667                 // But first we must change the stm of the engine, as it refused the last move
7668                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7669                 if(WhiteOnMove(forwardMostMove)) {
7670                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7671                     SendBoard(cps, forwardMostMove); // kludgeless board
7672                 } else {
7673                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7674                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7675                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7676                 }
7677           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7678             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7679                  gameMode == TwoMachinesPlay)
7680               SendToProgram("go\n", cps);
7681             return;
7682       } else
7683         if (gameMode == PlayFromGameFile) {
7684             /* Stop reading this game file */
7685             gameMode = EditGame;
7686             ModeHighlight();
7687         }
7688         /* [HGM] illegal-move claim should forfeit game when Xboard */
7689         /* only passes fully legal moves                            */
7690         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7691             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7692                                 "False illegal-move claim", GE_XBOARD );
7693             return; // do not take back move we tested as valid
7694         }
7695         currentMove = forwardMostMove-1;
7696         DisplayMove(currentMove-1); /* before DisplayMoveError */
7697         SwitchClocks(forwardMostMove-1); // [HGM] race
7698         DisplayBothClocks();
7699         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7700                 parseList[currentMove], _(cps->which));
7701         DisplayMoveError(buf1);
7702         DrawPosition(FALSE, boards[currentMove]);
7703         return;
7704     }
7705     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7706         /* Program has a broken "time" command that
7707            outputs a string not ending in newline.
7708            Don't use it. */
7709         cps->sendTime = 0;
7710     }
7711
7712     /*
7713      * If chess program startup fails, exit with an error message.
7714      * Attempts to recover here are futile.
7715      */
7716     if ((StrStr(message, "unknown host") != NULL)
7717         || (StrStr(message, "No remote directory") != NULL)
7718         || (StrStr(message, "not found") != NULL)
7719         || (StrStr(message, "No such file") != NULL)
7720         || (StrStr(message, "can't alloc") != NULL)
7721         || (StrStr(message, "Permission denied") != NULL)) {
7722
7723         cps->maybeThinking = FALSE;
7724         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7725                 _(cps->which), cps->program, cps->host, message);
7726         RemoveInputSource(cps->isr);
7727         DisplayFatalError(buf1, 0, 1);
7728         return;
7729     }
7730
7731     /*
7732      * Look for hint output
7733      */
7734     if (sscanf(message, "Hint: %s", buf1) == 1) {
7735         if (cps == &first && hintRequested) {
7736             hintRequested = FALSE;
7737             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7738                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7739                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7740                                     PosFlags(forwardMostMove),
7741                                     fromY, fromX, toY, toX, promoChar, buf1);
7742                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7743                 DisplayInformation(buf2);
7744             } else {
7745                 /* Hint move could not be parsed!? */
7746               snprintf(buf2, sizeof(buf2),
7747                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7748                         buf1, _(cps->which));
7749                 DisplayError(buf2, 0);
7750             }
7751         } else {
7752           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7753         }
7754         return;
7755     }
7756
7757     /*
7758      * Ignore other messages if game is not in progress
7759      */
7760     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7761         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7762
7763     /*
7764      * look for win, lose, draw, or draw offer
7765      */
7766     if (strncmp(message, "1-0", 3) == 0) {
7767         char *p, *q, *r = "";
7768         p = strchr(message, '{');
7769         if (p) {
7770             q = strchr(p, '}');
7771             if (q) {
7772                 *q = NULLCHAR;
7773                 r = p + 1;
7774             }
7775         }
7776         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7777         return;
7778     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
7789         if (strcmp(r, "Black resigns") == 0) {
7790             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7791             return;
7792         }
7793         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7794         return;
7795     } else if (strncmp(message, "1/2", 3) == 0) {
7796         char *p, *q, *r = "";
7797         p = strchr(message, '{');
7798         if (p) {
7799             q = strchr(p, '}');
7800             if (q) {
7801                 *q = NULLCHAR;
7802                 r = p + 1;
7803             }
7804         }
7805
7806         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7807         return;
7808
7809     } else if (strncmp(message, "White resign", 12) == 0) {
7810         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7811         return;
7812     } else if (strncmp(message, "Black resign", 12) == 0) {
7813         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7814         return;
7815     } else if (strncmp(message, "White matches", 13) == 0 ||
7816                strncmp(message, "Black matches", 13) == 0   ) {
7817         /* [HGM] ignore GNUShogi noises */
7818         return;
7819     } else if (strncmp(message, "White", 5) == 0 &&
7820                message[5] != '(' &&
7821                StrStr(message, "Black") == NULL) {
7822         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7823         return;
7824     } else if (strncmp(message, "Black", 5) == 0 &&
7825                message[5] != '(') {
7826         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7827         return;
7828     } else if (strcmp(message, "resign") == 0 ||
7829                strcmp(message, "computer resigns") == 0) {
7830         switch (gameMode) {
7831           case MachinePlaysBlack:
7832           case IcsPlayingBlack:
7833             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7834             break;
7835           case MachinePlaysWhite:
7836           case IcsPlayingWhite:
7837             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7838             break;
7839           case TwoMachinesPlay:
7840             if (cps->twoMachinesColor[0] == 'w')
7841               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7842             else
7843               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7844             break;
7845           default:
7846             /* can't happen */
7847             break;
7848         }
7849         return;
7850     } else if (strncmp(message, "opponent mates", 14) == 0) {
7851         switch (gameMode) {
7852           case MachinePlaysBlack:
7853           case IcsPlayingBlack:
7854             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7855             break;
7856           case MachinePlaysWhite:
7857           case IcsPlayingWhite:
7858             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7859             break;
7860           case TwoMachinesPlay:
7861             if (cps->twoMachinesColor[0] == 'w')
7862               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7863             else
7864               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7865             break;
7866           default:
7867             /* can't happen */
7868             break;
7869         }
7870         return;
7871     } else if (strncmp(message, "computer mates", 14) == 0) {
7872         switch (gameMode) {
7873           case MachinePlaysBlack:
7874           case IcsPlayingBlack:
7875             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7876             break;
7877           case MachinePlaysWhite:
7878           case IcsPlayingWhite:
7879             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7880             break;
7881           case TwoMachinesPlay:
7882             if (cps->twoMachinesColor[0] == 'w')
7883               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7884             else
7885               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7886             break;
7887           default:
7888             /* can't happen */
7889             break;
7890         }
7891         return;
7892     } else if (strncmp(message, "checkmate", 9) == 0) {
7893         if (WhiteOnMove(forwardMostMove)) {
7894             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7895         } else {
7896             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7897         }
7898         return;
7899     } else if (strstr(message, "Draw") != NULL ||
7900                strstr(message, "game is a draw") != NULL) {
7901         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7902         return;
7903     } else if (strstr(message, "offer") != NULL &&
7904                strstr(message, "draw") != NULL) {
7905 #if ZIPPY
7906         if (appData.zippyPlay && first.initDone) {
7907             /* Relay offer to ICS */
7908             SendToICS(ics_prefix);
7909             SendToICS("draw\n");
7910         }
7911 #endif
7912         cps->offeredDraw = 2; /* valid until this engine moves twice */
7913         if (gameMode == TwoMachinesPlay) {
7914             if (cps->other->offeredDraw) {
7915                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7916             /* [HGM] in two-machine mode we delay relaying draw offer      */
7917             /* until after we also have move, to see if it is really claim */
7918             }
7919         } else if (gameMode == MachinePlaysWhite ||
7920                    gameMode == MachinePlaysBlack) {
7921           if (userOfferedDraw) {
7922             DisplayInformation(_("Machine accepts your draw offer"));
7923             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7924           } else {
7925             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7926           }
7927         }
7928     }
7929
7930
7931     /*
7932      * Look for thinking output
7933      */
7934     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7935           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7936                                 ) {
7937         int plylev, mvleft, mvtot, curscore, time;
7938         char mvname[MOVE_LEN];
7939         u64 nodes; // [DM]
7940         char plyext;
7941         int ignore = FALSE;
7942         int prefixHint = FALSE;
7943         mvname[0] = NULLCHAR;
7944
7945         switch (gameMode) {
7946           case MachinePlaysBlack:
7947           case IcsPlayingBlack:
7948             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7949             break;
7950           case MachinePlaysWhite:
7951           case IcsPlayingWhite:
7952             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7953             break;
7954           case AnalyzeMode:
7955           case AnalyzeFile:
7956             break;
7957           case IcsObserving: /* [DM] icsEngineAnalyze */
7958             if (!appData.icsEngineAnalyze) ignore = TRUE;
7959             break;
7960           case TwoMachinesPlay:
7961             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7962                 ignore = TRUE;
7963             }
7964             break;
7965           default:
7966             ignore = TRUE;
7967             break;
7968         }
7969
7970         if (!ignore) {
7971             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7972             buf1[0] = NULLCHAR;
7973             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7974                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7975
7976                 if (plyext != ' ' && plyext != '\t') {
7977                     time *= 100;
7978                 }
7979
7980                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7981                 if( cps->scoreIsAbsolute &&
7982                     ( gameMode == MachinePlaysBlack ||
7983                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7984                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7985                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7986                      !WhiteOnMove(currentMove)
7987                     ) )
7988                 {
7989                     curscore = -curscore;
7990                 }
7991
7992
7993                 tempStats.depth = plylev;
7994                 tempStats.nodes = nodes;
7995                 tempStats.time = time;
7996                 tempStats.score = curscore;
7997                 tempStats.got_only_move = 0;
7998
7999                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8000                         int ticklen;
8001
8002                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8003                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8004                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8005                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8006                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8007                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8008                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8009                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8010                 }
8011
8012                 /* Buffer overflow protection */
8013                 if (buf1[0] != NULLCHAR) {
8014                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8015                         && appData.debugMode) {
8016                         fprintf(debugFP,
8017                                 "PV is too long; using the first %u bytes.\n",
8018                                 (unsigned) sizeof(tempStats.movelist) - 1);
8019                     }
8020
8021                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8022                 } else {
8023                     sprintf(tempStats.movelist, " no PV\n");
8024                 }
8025
8026                 if (tempStats.seen_stat) {
8027                     tempStats.ok_to_send = 1;
8028                 }
8029
8030                 if (strchr(tempStats.movelist, '(') != NULL) {
8031                     tempStats.line_is_book = 1;
8032                     tempStats.nr_moves = 0;
8033                     tempStats.moves_left = 0;
8034                 } else {
8035                     tempStats.line_is_book = 0;
8036                 }
8037
8038                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8039                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8040
8041                 SendProgramStatsToFrontend( cps, &tempStats );
8042
8043                 /*
8044                     [AS] Protect the thinkOutput buffer from overflow... this
8045                     is only useful if buf1 hasn't overflowed first!
8046                 */
8047                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8048                          plylev,
8049                          (gameMode == TwoMachinesPlay ?
8050                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8051                          ((double) curscore) / 100.0,
8052                          prefixHint ? lastHint : "",
8053                          prefixHint ? " " : "" );
8054
8055                 if( buf1[0] != NULLCHAR ) {
8056                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8057
8058                     if( strlen(buf1) > max_len ) {
8059                         if( appData.debugMode) {
8060                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8061                         }
8062                         buf1[max_len+1] = '\0';
8063                     }
8064
8065                     strcat( thinkOutput, buf1 );
8066                 }
8067
8068                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8069                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8070                     DisplayMove(currentMove - 1);
8071                 }
8072                 return;
8073
8074             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8075                 /* crafty (9.25+) says "(only move) <move>"
8076                  * if there is only 1 legal move
8077                  */
8078                 sscanf(p, "(only move) %s", buf1);
8079                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8080                 sprintf(programStats.movelist, "%s (only move)", buf1);
8081                 programStats.depth = 1;
8082                 programStats.nr_moves = 1;
8083                 programStats.moves_left = 1;
8084                 programStats.nodes = 1;
8085                 programStats.time = 1;
8086                 programStats.got_only_move = 1;
8087
8088                 /* Not really, but we also use this member to
8089                    mean "line isn't going to change" (Crafty
8090                    isn't searching, so stats won't change) */
8091                 programStats.line_is_book = 1;
8092
8093                 SendProgramStatsToFrontend( cps, &programStats );
8094
8095                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8096                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8097                     DisplayMove(currentMove - 1);
8098                 }
8099                 return;
8100             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8101                               &time, &nodes, &plylev, &mvleft,
8102                               &mvtot, mvname) >= 5) {
8103                 /* The stat01: line is from Crafty (9.29+) in response
8104                    to the "." command */
8105                 programStats.seen_stat = 1;
8106                 cps->maybeThinking = TRUE;
8107
8108                 if (programStats.got_only_move || !appData.periodicUpdates)
8109                   return;
8110
8111                 programStats.depth = plylev;
8112                 programStats.time = time;
8113                 programStats.nodes = nodes;
8114                 programStats.moves_left = mvleft;
8115                 programStats.nr_moves = mvtot;
8116                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8117                 programStats.ok_to_send = 1;
8118                 programStats.movelist[0] = '\0';
8119
8120                 SendProgramStatsToFrontend( cps, &programStats );
8121
8122                 return;
8123
8124             } else if (strncmp(message,"++",2) == 0) {
8125                 /* Crafty 9.29+ outputs this */
8126                 programStats.got_fail = 2;
8127                 return;
8128
8129             } else if (strncmp(message,"--",2) == 0) {
8130                 /* Crafty 9.29+ outputs this */
8131                 programStats.got_fail = 1;
8132                 return;
8133
8134             } else if (thinkOutput[0] != NULLCHAR &&
8135                        strncmp(message, "    ", 4) == 0) {
8136                 unsigned message_len;
8137
8138                 p = message;
8139                 while (*p && *p == ' ') p++;
8140
8141                 message_len = strlen( p );
8142
8143                 /* [AS] Avoid buffer overflow */
8144                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8145                     strcat(thinkOutput, " ");
8146                     strcat(thinkOutput, p);
8147                 }
8148
8149                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8150                     strcat(programStats.movelist, " ");
8151                     strcat(programStats.movelist, p);
8152                 }
8153
8154                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8155                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8156                     DisplayMove(currentMove - 1);
8157                 }
8158                 return;
8159             }
8160         }
8161         else {
8162             buf1[0] = NULLCHAR;
8163
8164             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8165                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8166             {
8167                 ChessProgramStats cpstats;
8168
8169                 if (plyext != ' ' && plyext != '\t') {
8170                     time *= 100;
8171                 }
8172
8173                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8174                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8175                     curscore = -curscore;
8176                 }
8177
8178                 cpstats.depth = plylev;
8179                 cpstats.nodes = nodes;
8180                 cpstats.time = time;
8181                 cpstats.score = curscore;
8182                 cpstats.got_only_move = 0;
8183                 cpstats.movelist[0] = '\0';
8184
8185                 if (buf1[0] != NULLCHAR) {
8186                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8187                 }
8188
8189                 cpstats.ok_to_send = 0;
8190                 cpstats.line_is_book = 0;
8191                 cpstats.nr_moves = 0;
8192                 cpstats.moves_left = 0;
8193
8194                 SendProgramStatsToFrontend( cps, &cpstats );
8195             }
8196         }
8197     }
8198 }
8199
8200
8201 /* Parse a game score from the character string "game", and
8202    record it as the history of the current game.  The game
8203    score is NOT assumed to start from the standard position.
8204    The display is not updated in any way.
8205    */
8206 void
8207 ParseGameHistory(game)
8208      char *game;
8209 {
8210     ChessMove moveType;
8211     int fromX, fromY, toX, toY, boardIndex;
8212     char promoChar;
8213     char *p, *q;
8214     char buf[MSG_SIZ];
8215
8216     if (appData.debugMode)
8217       fprintf(debugFP, "Parsing game history: %s\n", game);
8218
8219     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8220     gameInfo.site = StrSave(appData.icsHost);
8221     gameInfo.date = PGNDate();
8222     gameInfo.round = StrSave("-");
8223
8224     /* Parse out names of players */
8225     while (*game == ' ') game++;
8226     p = buf;
8227     while (*game != ' ') *p++ = *game++;
8228     *p = NULLCHAR;
8229     gameInfo.white = StrSave(buf);
8230     while (*game == ' ') game++;
8231     p = buf;
8232     while (*game != ' ' && *game != '\n') *p++ = *game++;
8233     *p = NULLCHAR;
8234     gameInfo.black = StrSave(buf);
8235
8236     /* Parse moves */
8237     boardIndex = blackPlaysFirst ? 1 : 0;
8238     yynewstr(game);
8239     for (;;) {
8240         yyboardindex = boardIndex;
8241         moveType = (ChessMove) Myylex();
8242         switch (moveType) {
8243           case IllegalMove:             /* maybe suicide chess, etc. */
8244   if (appData.debugMode) {
8245     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8246     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8247     setbuf(debugFP, NULL);
8248   }
8249           case WhitePromotion:
8250           case BlackPromotion:
8251           case WhiteNonPromotion:
8252           case BlackNonPromotion:
8253           case NormalMove:
8254           case WhiteCapturesEnPassant:
8255           case BlackCapturesEnPassant:
8256           case WhiteKingSideCastle:
8257           case WhiteQueenSideCastle:
8258           case BlackKingSideCastle:
8259           case BlackQueenSideCastle:
8260           case WhiteKingSideCastleWild:
8261           case WhiteQueenSideCastleWild:
8262           case BlackKingSideCastleWild:
8263           case BlackQueenSideCastleWild:
8264           /* PUSH Fabien */
8265           case WhiteHSideCastleFR:
8266           case WhiteASideCastleFR:
8267           case BlackHSideCastleFR:
8268           case BlackASideCastleFR:
8269           /* POP Fabien */
8270             fromX = currentMoveString[0] - AAA;
8271             fromY = currentMoveString[1] - ONE;
8272             toX = currentMoveString[2] - AAA;
8273             toY = currentMoveString[3] - ONE;
8274             promoChar = currentMoveString[4];
8275             break;
8276           case WhiteDrop:
8277           case BlackDrop:
8278             fromX = moveType == WhiteDrop ?
8279               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8280             (int) CharToPiece(ToLower(currentMoveString[0]));
8281             fromY = DROP_RANK;
8282             toX = currentMoveString[2] - AAA;
8283             toY = currentMoveString[3] - ONE;
8284             promoChar = NULLCHAR;
8285             break;
8286           case AmbiguousMove:
8287             /* bug? */
8288             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8289   if (appData.debugMode) {
8290     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8291     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8292     setbuf(debugFP, NULL);
8293   }
8294             DisplayError(buf, 0);
8295             return;
8296           case ImpossibleMove:
8297             /* bug? */
8298             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8299   if (appData.debugMode) {
8300     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8301     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8302     setbuf(debugFP, NULL);
8303   }
8304             DisplayError(buf, 0);
8305             return;
8306           case EndOfFile:
8307             if (boardIndex < backwardMostMove) {
8308                 /* Oops, gap.  How did that happen? */
8309                 DisplayError(_("Gap in move list"), 0);
8310                 return;
8311             }
8312             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8313             if (boardIndex > forwardMostMove) {
8314                 forwardMostMove = boardIndex;
8315             }
8316             return;
8317           case ElapsedTime:
8318             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8319                 strcat(parseList[boardIndex-1], " ");
8320                 strcat(parseList[boardIndex-1], yy_text);
8321             }
8322             continue;
8323           case Comment:
8324           case PGNTag:
8325           case NAG:
8326           default:
8327             /* ignore */
8328             continue;
8329           case WhiteWins:
8330           case BlackWins:
8331           case GameIsDrawn:
8332           case GameUnfinished:
8333             if (gameMode == IcsExamining) {
8334                 if (boardIndex < backwardMostMove) {
8335                     /* Oops, gap.  How did that happen? */
8336                     return;
8337                 }
8338                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8339                 return;
8340             }
8341             gameInfo.result = moveType;
8342             p = strchr(yy_text, '{');
8343             if (p == NULL) p = strchr(yy_text, '(');
8344             if (p == NULL) {
8345                 p = yy_text;
8346                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8347             } else {
8348                 q = strchr(p, *p == '{' ? '}' : ')');
8349                 if (q != NULL) *q = NULLCHAR;
8350                 p++;
8351             }
8352             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8353             gameInfo.resultDetails = StrSave(p);
8354             continue;
8355         }
8356         if (boardIndex >= forwardMostMove &&
8357             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8358             backwardMostMove = blackPlaysFirst ? 1 : 0;
8359             return;
8360         }
8361         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8362                                  fromY, fromX, toY, toX, promoChar,
8363                                  parseList[boardIndex]);
8364         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8365         /* currentMoveString is set as a side-effect of yylex */
8366         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8367         strcat(moveList[boardIndex], "\n");
8368         boardIndex++;
8369         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8370         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8371           case MT_NONE:
8372           case MT_STALEMATE:
8373           default:
8374             break;
8375           case MT_CHECK:
8376             if(gameInfo.variant != VariantShogi)
8377                 strcat(parseList[boardIndex - 1], "+");
8378             break;
8379           case MT_CHECKMATE:
8380           case MT_STAINMATE:
8381             strcat(parseList[boardIndex - 1], "#");
8382             break;
8383         }
8384     }
8385 }
8386
8387
8388 /* Apply a move to the given board  */
8389 void
8390 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8391      int fromX, fromY, toX, toY;
8392      int promoChar;
8393      Board board;
8394 {
8395   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8396   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8397
8398     /* [HGM] compute & store e.p. status and castling rights for new position */
8399     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8400
8401       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8402       oldEP = (signed char)board[EP_STATUS];
8403       board[EP_STATUS] = EP_NONE;
8404
8405       if( board[toY][toX] != EmptySquare )
8406            board[EP_STATUS] = EP_CAPTURE;
8407
8408   if (fromY == DROP_RANK) {
8409         /* must be first */
8410         piece = board[toY][toX] = (ChessSquare) fromX;
8411   } else {
8412       int i;
8413
8414       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8415            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8416                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8417       } else
8418       if( board[fromY][fromX] == WhitePawn ) {
8419            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8420                board[EP_STATUS] = EP_PAWN_MOVE;
8421            if( toY-fromY==2) {
8422                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8423                         gameInfo.variant != VariantBerolina || toX < fromX)
8424                       board[EP_STATUS] = toX | berolina;
8425                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8426                         gameInfo.variant != VariantBerolina || toX > fromX)
8427                       board[EP_STATUS] = toX;
8428            }
8429       } else
8430       if( board[fromY][fromX] == BlackPawn ) {
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] == WhitePawn &&
8435                         gameInfo.variant != VariantBerolina || toX < fromX)
8436                       board[EP_STATUS] = toX | berolina;
8437                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8438                         gameInfo.variant != VariantBerolina || toX > fromX)
8439                       board[EP_STATUS] = toX;
8440            }
8441        }
8442
8443        for(i=0; i<nrCastlingRights; i++) {
8444            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8445               board[CASTLING][i] == toX   && castlingRank[i] == toY
8446              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8447        }
8448
8449      if (fromX == toX && fromY == toY) return;
8450
8451      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8452      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8453      if(gameInfo.variant == VariantKnightmate)
8454          king += (int) WhiteUnicorn - (int) WhiteKing;
8455
8456     /* Code added by Tord: */
8457     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8458     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8459         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8460       board[fromY][fromX] = EmptySquare;
8461       board[toY][toX] = EmptySquare;
8462       if((toX > fromX) != (piece == WhiteRook)) {
8463         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8464       } else {
8465         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8466       }
8467     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8468                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8469       board[fromY][fromX] = EmptySquare;
8470       board[toY][toX] = EmptySquare;
8471       if((toX > fromX) != (piece == BlackRook)) {
8472         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8473       } else {
8474         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8475       }
8476     /* End of code added by Tord */
8477
8478     } else if (board[fromY][fromX] == king
8479         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8480         && toY == fromY && toX > fromX+1) {
8481         board[fromY][fromX] = EmptySquare;
8482         board[toY][toX] = king;
8483         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8484         board[fromY][BOARD_RGHT-1] = EmptySquare;
8485     } else if (board[fromY][fromX] == king
8486         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8487                && toY == fromY && toX < fromX-1) {
8488         board[fromY][fromX] = EmptySquare;
8489         board[toY][toX] = king;
8490         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8491         board[fromY][BOARD_LEFT] = EmptySquare;
8492     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8493                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8494                && toY >= BOARD_HEIGHT-promoRank
8495                ) {
8496         /* white pawn promotion */
8497         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8498         if (board[toY][toX] == EmptySquare) {
8499             board[toY][toX] = WhiteQueen;
8500         }
8501         if(gameInfo.variant==VariantBughouse ||
8502            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8503             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8504         board[fromY][fromX] = EmptySquare;
8505     } else if ((fromY == BOARD_HEIGHT-4)
8506                && (toX != fromX)
8507                && gameInfo.variant != VariantXiangqi
8508                && gameInfo.variant != VariantBerolina
8509                && (board[fromY][fromX] == WhitePawn)
8510                && (board[toY][toX] == EmptySquare)) {
8511         board[fromY][fromX] = EmptySquare;
8512         board[toY][toX] = WhitePawn;
8513         captured = board[toY - 1][toX];
8514         board[toY - 1][toX] = EmptySquare;
8515     } else if ((fromY == BOARD_HEIGHT-4)
8516                && (toX == fromX)
8517                && gameInfo.variant == VariantBerolina
8518                && (board[fromY][fromX] == WhitePawn)
8519                && (board[toY][toX] == EmptySquare)) {
8520         board[fromY][fromX] = EmptySquare;
8521         board[toY][toX] = WhitePawn;
8522         if(oldEP & EP_BEROLIN_A) {
8523                 captured = board[fromY][fromX-1];
8524                 board[fromY][fromX-1] = EmptySquare;
8525         }else{  captured = board[fromY][fromX+1];
8526                 board[fromY][fromX+1] = EmptySquare;
8527         }
8528     } else if (board[fromY][fromX] == king
8529         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8530                && toY == fromY && toX > fromX+1) {
8531         board[fromY][fromX] = EmptySquare;
8532         board[toY][toX] = king;
8533         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8534         board[fromY][BOARD_RGHT-1] = EmptySquare;
8535     } else if (board[fromY][fromX] == king
8536         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8537                && toY == fromY && toX < fromX-1) {
8538         board[fromY][fromX] = EmptySquare;
8539         board[toY][toX] = king;
8540         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8541         board[fromY][BOARD_LEFT] = EmptySquare;
8542     } else if (fromY == 7 && fromX == 3
8543                && board[fromY][fromX] == BlackKing
8544                && toY == 7 && toX == 5) {
8545         board[fromY][fromX] = EmptySquare;
8546         board[toY][toX] = BlackKing;
8547         board[fromY][7] = EmptySquare;
8548         board[toY][4] = BlackRook;
8549     } else if (fromY == 7 && fromX == 3
8550                && board[fromY][fromX] == BlackKing
8551                && toY == 7 && toX == 1) {
8552         board[fromY][fromX] = EmptySquare;
8553         board[toY][toX] = BlackKing;
8554         board[fromY][0] = EmptySquare;
8555         board[toY][2] = BlackRook;
8556     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8557                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8558                && toY < promoRank
8559                ) {
8560         /* black pawn promotion */
8561         board[toY][toX] = CharToPiece(ToLower(promoChar));
8562         if (board[toY][toX] == EmptySquare) {
8563             board[toY][toX] = BlackQueen;
8564         }
8565         if(gameInfo.variant==VariantBughouse ||
8566            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8567             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8568         board[fromY][fromX] = EmptySquare;
8569     } else if ((fromY == 3)
8570                && (toX != fromX)
8571                && gameInfo.variant != VariantXiangqi
8572                && gameInfo.variant != VariantBerolina
8573                && (board[fromY][fromX] == BlackPawn)
8574                && (board[toY][toX] == EmptySquare)) {
8575         board[fromY][fromX] = EmptySquare;
8576         board[toY][toX] = BlackPawn;
8577         captured = board[toY + 1][toX];
8578         board[toY + 1][toX] = EmptySquare;
8579     } else if ((fromY == 3)
8580                && (toX == fromX)
8581                && gameInfo.variant == VariantBerolina
8582                && (board[fromY][fromX] == BlackPawn)
8583                && (board[toY][toX] == EmptySquare)) {
8584         board[fromY][fromX] = EmptySquare;
8585         board[toY][toX] = BlackPawn;
8586         if(oldEP & EP_BEROLIN_A) {
8587                 captured = board[fromY][fromX-1];
8588                 board[fromY][fromX-1] = EmptySquare;
8589         }else{  captured = board[fromY][fromX+1];
8590                 board[fromY][fromX+1] = EmptySquare;
8591         }
8592     } else {
8593         board[toY][toX] = board[fromY][fromX];
8594         board[fromY][fromX] = EmptySquare;
8595     }
8596   }
8597
8598     if (gameInfo.holdingsWidth != 0) {
8599
8600       /* !!A lot more code needs to be written to support holdings  */
8601       /* [HGM] OK, so I have written it. Holdings are stored in the */
8602       /* penultimate board files, so they are automaticlly stored   */
8603       /* in the game history.                                       */
8604       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8605                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8606         /* Delete from holdings, by decreasing count */
8607         /* and erasing image if necessary            */
8608         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8609         if(p < (int) BlackPawn) { /* white drop */
8610              p -= (int)WhitePawn;
8611                  p = PieceToNumber((ChessSquare)p);
8612              if(p >= gameInfo.holdingsSize) p = 0;
8613              if(--board[p][BOARD_WIDTH-2] <= 0)
8614                   board[p][BOARD_WIDTH-1] = EmptySquare;
8615              if((int)board[p][BOARD_WIDTH-2] < 0)
8616                         board[p][BOARD_WIDTH-2] = 0;
8617         } else {                  /* black drop */
8618              p -= (int)BlackPawn;
8619                  p = PieceToNumber((ChessSquare)p);
8620              if(p >= gameInfo.holdingsSize) p = 0;
8621              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8622                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8623              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8624                         board[BOARD_HEIGHT-1-p][1] = 0;
8625         }
8626       }
8627       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8628           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8629         /* [HGM] holdings: Add to holdings, if holdings exist */
8630         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8631                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8632                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8633         }
8634         p = (int) captured;
8635         if (p >= (int) BlackPawn) {
8636           p -= (int)BlackPawn;
8637           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8638                   /* in Shogi restore piece to its original  first */
8639                   captured = (ChessSquare) (DEMOTED captured);
8640                   p = DEMOTED p;
8641           }
8642           p = PieceToNumber((ChessSquare)p);
8643           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8644           board[p][BOARD_WIDTH-2]++;
8645           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8646         } else {
8647           p -= (int)WhitePawn;
8648           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8649                   captured = (ChessSquare) (DEMOTED captured);
8650                   p = DEMOTED p;
8651           }
8652           p = PieceToNumber((ChessSquare)p);
8653           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8654           board[BOARD_HEIGHT-1-p][1]++;
8655           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8656         }
8657       }
8658     } else if (gameInfo.variant == VariantAtomic) {
8659       if (captured != EmptySquare) {
8660         int y, x;
8661         for (y = toY-1; y <= toY+1; y++) {
8662           for (x = toX-1; x <= toX+1; x++) {
8663             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8664                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8665               board[y][x] = EmptySquare;
8666             }
8667           }
8668         }
8669         board[toY][toX] = EmptySquare;
8670       }
8671     }
8672     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8673         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8674     } else
8675     if(promoChar == '+') {
8676         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8677         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8678     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8679         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8680     }
8681     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8682                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8683         // [HGM] superchess: take promotion piece out of holdings
8684         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8685         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8686             if(!--board[k][BOARD_WIDTH-2])
8687                 board[k][BOARD_WIDTH-1] = EmptySquare;
8688         } else {
8689             if(!--board[BOARD_HEIGHT-1-k][1])
8690                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8691         }
8692     }
8693
8694 }
8695
8696 /* Updates forwardMostMove */
8697 void
8698 MakeMove(fromX, fromY, toX, toY, promoChar)
8699      int fromX, fromY, toX, toY;
8700      int promoChar;
8701 {
8702 //    forwardMostMove++; // [HGM] bare: moved downstream
8703
8704     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8705         int timeLeft; static int lastLoadFlag=0; int king, piece;
8706         piece = boards[forwardMostMove][fromY][fromX];
8707         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8708         if(gameInfo.variant == VariantKnightmate)
8709             king += (int) WhiteUnicorn - (int) WhiteKing;
8710         if(forwardMostMove == 0) {
8711             if(blackPlaysFirst)
8712                 fprintf(serverMoves, "%s;", second.tidy);
8713             fprintf(serverMoves, "%s;", first.tidy);
8714             if(!blackPlaysFirst)
8715                 fprintf(serverMoves, "%s;", second.tidy);
8716         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8717         lastLoadFlag = loadFlag;
8718         // print base move
8719         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8720         // print castling suffix
8721         if( toY == fromY && piece == king ) {
8722             if(toX-fromX > 1)
8723                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8724             if(fromX-toX >1)
8725                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8726         }
8727         // e.p. suffix
8728         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8729              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8730              boards[forwardMostMove][toY][toX] == EmptySquare
8731              && fromX != toX && fromY != toY)
8732                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8733         // promotion suffix
8734         if(promoChar != NULLCHAR)
8735                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8736         if(!loadFlag) {
8737             fprintf(serverMoves, "/%d/%d",
8738                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8739             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8740             else                      timeLeft = blackTimeRemaining/1000;
8741             fprintf(serverMoves, "/%d", timeLeft);
8742         }
8743         fflush(serverMoves);
8744     }
8745
8746     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8747       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8748                         0, 1);
8749       return;
8750     }
8751     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8752     if (commentList[forwardMostMove+1] != NULL) {
8753         free(commentList[forwardMostMove+1]);
8754         commentList[forwardMostMove+1] = NULL;
8755     }
8756     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8757     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8758     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8759     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8760     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8761     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8762     gameInfo.result = GameUnfinished;
8763     if (gameInfo.resultDetails != NULL) {
8764         free(gameInfo.resultDetails);
8765         gameInfo.resultDetails = NULL;
8766     }
8767     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8768                               moveList[forwardMostMove - 1]);
8769     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8770                              PosFlags(forwardMostMove - 1),
8771                              fromY, fromX, toY, toX, promoChar,
8772                              parseList[forwardMostMove - 1]);
8773     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8774       case MT_NONE:
8775       case MT_STALEMATE:
8776       default:
8777         break;
8778       case MT_CHECK:
8779         if(gameInfo.variant != VariantShogi)
8780             strcat(parseList[forwardMostMove - 1], "+");
8781         break;
8782       case MT_CHECKMATE:
8783       case MT_STAINMATE:
8784         strcat(parseList[forwardMostMove - 1], "#");
8785         break;
8786     }
8787     if (appData.debugMode) {
8788         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8789     }
8790
8791 }
8792
8793 /* Updates currentMove if not pausing */
8794 void
8795 ShowMove(fromX, fromY, toX, toY)
8796 {
8797     int instant = (gameMode == PlayFromGameFile) ?
8798         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8799     if(appData.noGUI) return;
8800     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8801         if (!instant) {
8802             if (forwardMostMove == currentMove + 1) {
8803                 AnimateMove(boards[forwardMostMove - 1],
8804                             fromX, fromY, toX, toY);
8805             }
8806             if (appData.highlightLastMove) {
8807                 SetHighlights(fromX, fromY, toX, toY);
8808             }
8809         }
8810         currentMove = forwardMostMove;
8811     }
8812
8813     if (instant) return;
8814
8815     DisplayMove(currentMove - 1);
8816     DrawPosition(FALSE, boards[currentMove]);
8817     DisplayBothClocks();
8818     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8819 }
8820
8821 void SendEgtPath(ChessProgramState *cps)
8822 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8823         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8824
8825         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8826
8827         while(*p) {
8828             char c, *q = name+1, *r, *s;
8829
8830             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8831             while(*p && *p != ',') *q++ = *p++;
8832             *q++ = ':'; *q = 0;
8833             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8834                 strcmp(name, ",nalimov:") == 0 ) {
8835                 // take nalimov path from the menu-changeable option first, if it is defined
8836               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8837                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8838             } else
8839             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8840                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8841                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8842                 s = r = StrStr(s, ":") + 1; // beginning of path info
8843                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8844                 c = *r; *r = 0;             // temporarily null-terminate path info
8845                     *--q = 0;               // strip of trailig ':' from name
8846                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8847                 *r = c;
8848                 SendToProgram(buf,cps);     // send egtbpath command for this format
8849             }
8850             if(*p == ',') p++; // read away comma to position for next format name
8851         }
8852 }
8853
8854 void
8855 InitChessProgram(cps, setup)
8856      ChessProgramState *cps;
8857      int setup; /* [HGM] needed to setup FRC opening position */
8858 {
8859     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8860     if (appData.noChessProgram) return;
8861     hintRequested = FALSE;
8862     bookRequested = FALSE;
8863
8864     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8865     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8866     if(cps->memSize) { /* [HGM] memory */
8867       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8868         SendToProgram(buf, cps);
8869     }
8870     SendEgtPath(cps); /* [HGM] EGT */
8871     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8872       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8873         SendToProgram(buf, cps);
8874     }
8875
8876     SendToProgram(cps->initString, cps);
8877     if (gameInfo.variant != VariantNormal &&
8878         gameInfo.variant != VariantLoadable
8879         /* [HGM] also send variant if board size non-standard */
8880         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8881                                             ) {
8882       char *v = VariantName(gameInfo.variant);
8883       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8884         /* [HGM] in protocol 1 we have to assume all variants valid */
8885         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8886         DisplayFatalError(buf, 0, 1);
8887         return;
8888       }
8889
8890       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8891       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8892       if( gameInfo.variant == VariantXiangqi )
8893            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8894       if( gameInfo.variant == VariantShogi )
8895            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8896       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8897            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8898       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8899           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8900            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8901       if( gameInfo.variant == VariantCourier )
8902            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8903       if( gameInfo.variant == VariantSuper )
8904            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8905       if( gameInfo.variant == VariantGreat )
8906            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8907       if( gameInfo.variant == VariantSChess )
8908            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8909
8910       if(overruled) {
8911         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8912                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8913            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8914            if(StrStr(cps->variants, b) == NULL) {
8915                // specific sized variant not known, check if general sizing allowed
8916                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8917                    if(StrStr(cps->variants, "boardsize") == NULL) {
8918                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8919                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8920                        DisplayFatalError(buf, 0, 1);
8921                        return;
8922                    }
8923                    /* [HGM] here we really should compare with the maximum supported board size */
8924                }
8925            }
8926       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8927       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8928       SendToProgram(buf, cps);
8929     }
8930     currentlyInitializedVariant = gameInfo.variant;
8931
8932     /* [HGM] send opening position in FRC to first engine */
8933     if(setup) {
8934           SendToProgram("force\n", cps);
8935           SendBoard(cps, 0);
8936           /* engine is now in force mode! Set flag to wake it up after first move. */
8937           setboardSpoiledMachineBlack = 1;
8938     }
8939
8940     if (cps->sendICS) {
8941       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8942       SendToProgram(buf, cps);
8943     }
8944     cps->maybeThinking = FALSE;
8945     cps->offeredDraw = 0;
8946     if (!appData.icsActive) {
8947         SendTimeControl(cps, movesPerSession, timeControl,
8948                         timeIncrement, appData.searchDepth,
8949                         searchTime);
8950     }
8951     if (appData.showThinking
8952         // [HGM] thinking: four options require thinking output to be sent
8953         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8954                                 ) {
8955         SendToProgram("post\n", cps);
8956     }
8957     SendToProgram("hard\n", cps);
8958     if (!appData.ponderNextMove) {
8959         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8960            it without being sure what state we are in first.  "hard"
8961            is not a toggle, so that one is OK.
8962          */
8963         SendToProgram("easy\n", cps);
8964     }
8965     if (cps->usePing) {
8966       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8967       SendToProgram(buf, cps);
8968     }
8969     cps->initDone = TRUE;
8970 }
8971
8972
8973 void
8974 StartChessProgram(cps)
8975      ChessProgramState *cps;
8976 {
8977     char buf[MSG_SIZ];
8978     int err;
8979
8980     if (appData.noChessProgram) return;
8981     cps->initDone = FALSE;
8982
8983     if (strcmp(cps->host, "localhost") == 0) {
8984         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8985     } else if (*appData.remoteShell == NULLCHAR) {
8986         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8987     } else {
8988         if (*appData.remoteUser == NULLCHAR) {
8989           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8990                     cps->program);
8991         } else {
8992           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8993                     cps->host, appData.remoteUser, cps->program);
8994         }
8995         err = StartChildProcess(buf, "", &cps->pr);
8996     }
8997
8998     if (err != 0) {
8999       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9000         DisplayFatalError(buf, err, 1);
9001         cps->pr = NoProc;
9002         cps->isr = NULL;
9003         return;
9004     }
9005
9006     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9007     if (cps->protocolVersion > 1) {
9008       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9009       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9010       cps->comboCnt = 0;  //                and values of combo boxes
9011       SendToProgram(buf, cps);
9012     } else {
9013       SendToProgram("xboard\n", cps);
9014     }
9015 }
9016
9017
9018 void
9019 TwoMachinesEventIfReady P((void))
9020 {
9021   if (first.lastPing != first.lastPong) {
9022     DisplayMessage("", _("Waiting for first chess program"));
9023     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9024     return;
9025   }
9026   if (second.lastPing != second.lastPong) {
9027     DisplayMessage("", _("Waiting for second chess program"));
9028     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9029     return;
9030   }
9031   ThawUI();
9032   TwoMachinesEvent();
9033 }
9034
9035 void
9036 NextMatchGame P((void))
9037 {
9038     int index; /* [HGM] autoinc: step load index during match */
9039     Reset(FALSE, TRUE);
9040     if (*appData.loadGameFile != NULLCHAR) {
9041         index = appData.loadGameIndex;
9042         if(index < 0) { // [HGM] autoinc
9043             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9044             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9045         }
9046         LoadGameFromFile(appData.loadGameFile,
9047                          index,
9048                          appData.loadGameFile, FALSE);
9049     } else if (*appData.loadPositionFile != NULLCHAR) {
9050         index = appData.loadPositionIndex;
9051         if(index < 0) { // [HGM] autoinc
9052             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9053             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9054         }
9055         LoadPositionFromFile(appData.loadPositionFile,
9056                              index,
9057                              appData.loadPositionFile);
9058     }
9059     TwoMachinesEventIfReady();
9060 }
9061
9062 void UserAdjudicationEvent( int result )
9063 {
9064     ChessMove gameResult = GameIsDrawn;
9065
9066     if( result > 0 ) {
9067         gameResult = WhiteWins;
9068     }
9069     else if( result < 0 ) {
9070         gameResult = BlackWins;
9071     }
9072
9073     if( gameMode == TwoMachinesPlay ) {
9074         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9075     }
9076 }
9077
9078
9079 // [HGM] save: calculate checksum of game to make games easily identifiable
9080 int StringCheckSum(char *s)
9081 {
9082         int i = 0;
9083         if(s==NULL) return 0;
9084         while(*s) i = i*259 + *s++;
9085         return i;
9086 }
9087
9088 int GameCheckSum()
9089 {
9090         int i, sum=0;
9091         for(i=backwardMostMove; i<forwardMostMove; i++) {
9092                 sum += pvInfoList[i].depth;
9093                 sum += StringCheckSum(parseList[i]);
9094                 sum += StringCheckSum(commentList[i]);
9095                 sum *= 261;
9096         }
9097         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9098         return sum + StringCheckSum(commentList[i]);
9099 } // end of save patch
9100
9101 void
9102 GameEnds(result, resultDetails, whosays)
9103      ChessMove result;
9104      char *resultDetails;
9105      int whosays;
9106 {
9107     GameMode nextGameMode;
9108     int isIcsGame;
9109     char buf[MSG_SIZ], popupRequested = 0;
9110
9111     if(endingGame) return; /* [HGM] crash: forbid recursion */
9112     endingGame = 1;
9113     if(twoBoards) { // [HGM] dual: switch back to one board
9114         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9115         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9116     }
9117     if (appData.debugMode) {
9118       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9119               result, resultDetails ? resultDetails : "(null)", whosays);
9120     }
9121
9122     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9123
9124     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9125         /* If we are playing on ICS, the server decides when the
9126            game is over, but the engine can offer to draw, claim
9127            a draw, or resign.
9128          */
9129 #if ZIPPY
9130         if (appData.zippyPlay && first.initDone) {
9131             if (result == GameIsDrawn) {
9132                 /* In case draw still needs to be claimed */
9133                 SendToICS(ics_prefix);
9134                 SendToICS("draw\n");
9135             } else if (StrCaseStr(resultDetails, "resign")) {
9136                 SendToICS(ics_prefix);
9137                 SendToICS("resign\n");
9138             }
9139         }
9140 #endif
9141         endingGame = 0; /* [HGM] crash */
9142         return;
9143     }
9144
9145     /* If we're loading the game from a file, stop */
9146     if (whosays == GE_FILE) {
9147       (void) StopLoadGameTimer();
9148       gameFileFP = NULL;
9149     }
9150
9151     /* Cancel draw offers */
9152     first.offeredDraw = second.offeredDraw = 0;
9153
9154     /* If this is an ICS game, only ICS can really say it's done;
9155        if not, anyone can. */
9156     isIcsGame = (gameMode == IcsPlayingWhite ||
9157                  gameMode == IcsPlayingBlack ||
9158                  gameMode == IcsObserving    ||
9159                  gameMode == IcsExamining);
9160
9161     if (!isIcsGame || whosays == GE_ICS) {
9162         /* OK -- not an ICS game, or ICS said it was done */
9163         StopClocks();
9164         if (!isIcsGame && !appData.noChessProgram)
9165           SetUserThinkingEnables();
9166
9167         /* [HGM] if a machine claims the game end we verify this claim */
9168         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9169             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9170                 char claimer;
9171                 ChessMove trueResult = (ChessMove) -1;
9172
9173                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9174                                             first.twoMachinesColor[0] :
9175                                             second.twoMachinesColor[0] ;
9176
9177                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9178                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9179                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9180                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9181                 } else
9182                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9183                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9184                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9185                 } else
9186                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9187                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9188                 }
9189
9190                 // now verify win claims, but not in drop games, as we don't understand those yet
9191                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9192                                                  || gameInfo.variant == VariantGreat) &&
9193                     (result == WhiteWins && claimer == 'w' ||
9194                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9195                       if (appData.debugMode) {
9196                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9197                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9198                       }
9199                       if(result != trueResult) {
9200                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9201                               result = claimer == 'w' ? BlackWins : WhiteWins;
9202                               resultDetails = buf;
9203                       }
9204                 } else
9205                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9206                     && (forwardMostMove <= backwardMostMove ||
9207                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9208                         (claimer=='b')==(forwardMostMove&1))
9209                                                                                   ) {
9210                       /* [HGM] verify: draws that were not flagged are false claims */
9211                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9212                       result = claimer == 'w' ? BlackWins : WhiteWins;
9213                       resultDetails = buf;
9214                 }
9215                 /* (Claiming a loss is accepted no questions asked!) */
9216             }
9217             /* [HGM] bare: don't allow bare King to win */
9218             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9219                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9220                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9221                && result != GameIsDrawn)
9222             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9223                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9224                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9225                         if(p >= 0 && p <= (int)WhiteKing) k++;
9226                 }
9227                 if (appData.debugMode) {
9228                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9229                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9230                 }
9231                 if(k <= 1) {
9232                         result = GameIsDrawn;
9233                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9234                         resultDetails = buf;
9235                 }
9236             }
9237         }
9238
9239
9240         if(serverMoves != NULL && !loadFlag) { char c = '=';
9241             if(result==WhiteWins) c = '+';
9242             if(result==BlackWins) c = '-';
9243             if(resultDetails != NULL)
9244                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9245         }
9246         if (resultDetails != NULL) {
9247             gameInfo.result = result;
9248             gameInfo.resultDetails = StrSave(resultDetails);
9249
9250             /* display last move only if game was not loaded from file */
9251             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9252                 DisplayMove(currentMove - 1);
9253
9254             if (forwardMostMove != 0) {
9255                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9256                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9257                                                                 ) {
9258                     if (*appData.saveGameFile != NULLCHAR) {
9259                         SaveGameToFile(appData.saveGameFile, TRUE);
9260                     } else if (appData.autoSaveGames) {
9261                         AutoSaveGame();
9262                     }
9263                     if (*appData.savePositionFile != NULLCHAR) {
9264                         SavePositionToFile(appData.savePositionFile);
9265                     }
9266                 }
9267             }
9268
9269             /* Tell program how game ended in case it is learning */
9270             /* [HGM] Moved this to after saving the PGN, just in case */
9271             /* engine died and we got here through time loss. In that */
9272             /* case we will get a fatal error writing the pipe, which */
9273             /* would otherwise lose us the PGN.                       */
9274             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9275             /* output during GameEnds should never be fatal anymore   */
9276             if (gameMode == MachinePlaysWhite ||
9277                 gameMode == MachinePlaysBlack ||
9278                 gameMode == TwoMachinesPlay ||
9279                 gameMode == IcsPlayingWhite ||
9280                 gameMode == IcsPlayingBlack ||
9281                 gameMode == BeginningOfGame) {
9282                 char buf[MSG_SIZ];
9283                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9284                         resultDetails);
9285                 if (first.pr != NoProc) {
9286                     SendToProgram(buf, &first);
9287                 }
9288                 if (second.pr != NoProc &&
9289                     gameMode == TwoMachinesPlay) {
9290                     SendToProgram(buf, &second);
9291                 }
9292             }
9293         }
9294
9295         if (appData.icsActive) {
9296             if (appData.quietPlay &&
9297                 (gameMode == IcsPlayingWhite ||
9298                  gameMode == IcsPlayingBlack)) {
9299                 SendToICS(ics_prefix);
9300                 SendToICS("set shout 1\n");
9301             }
9302             nextGameMode = IcsIdle;
9303             ics_user_moved = FALSE;
9304             /* clean up premove.  It's ugly when the game has ended and the
9305              * premove highlights are still on the board.
9306              */
9307             if (gotPremove) {
9308               gotPremove = FALSE;
9309               ClearPremoveHighlights();
9310               DrawPosition(FALSE, boards[currentMove]);
9311             }
9312             if (whosays == GE_ICS) {
9313                 switch (result) {
9314                 case WhiteWins:
9315                     if (gameMode == IcsPlayingWhite)
9316                         PlayIcsWinSound();
9317                     else if(gameMode == IcsPlayingBlack)
9318                         PlayIcsLossSound();
9319                     break;
9320                 case BlackWins:
9321                     if (gameMode == IcsPlayingBlack)
9322                         PlayIcsWinSound();
9323                     else if(gameMode == IcsPlayingWhite)
9324                         PlayIcsLossSound();
9325                     break;
9326                 case GameIsDrawn:
9327                     PlayIcsDrawSound();
9328                     break;
9329                 default:
9330                     PlayIcsUnfinishedSound();
9331                 }
9332             }
9333         } else if (gameMode == EditGame ||
9334                    gameMode == PlayFromGameFile ||
9335                    gameMode == AnalyzeMode ||
9336                    gameMode == AnalyzeFile) {
9337             nextGameMode = gameMode;
9338         } else {
9339             nextGameMode = EndOfGame;
9340         }
9341         pausing = FALSE;
9342         ModeHighlight();
9343     } else {
9344         nextGameMode = gameMode;
9345     }
9346
9347     if (appData.noChessProgram) {
9348         gameMode = nextGameMode;
9349         ModeHighlight();
9350         endingGame = 0; /* [HGM] crash */
9351         return;
9352     }
9353
9354     if (first.reuse) {
9355         /* Put first chess program into idle state */
9356         if (first.pr != NoProc &&
9357             (gameMode == MachinePlaysWhite ||
9358              gameMode == MachinePlaysBlack ||
9359              gameMode == TwoMachinesPlay ||
9360              gameMode == IcsPlayingWhite ||
9361              gameMode == IcsPlayingBlack ||
9362              gameMode == BeginningOfGame)) {
9363             SendToProgram("force\n", &first);
9364             if (first.usePing) {
9365               char buf[MSG_SIZ];
9366               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9367               SendToProgram(buf, &first);
9368             }
9369         }
9370     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9371         /* Kill off first chess program */
9372         if (first.isr != NULL)
9373           RemoveInputSource(first.isr);
9374         first.isr = NULL;
9375
9376         if (first.pr != NoProc) {
9377             ExitAnalyzeMode();
9378             DoSleep( appData.delayBeforeQuit );
9379             SendToProgram("quit\n", &first);
9380             DoSleep( appData.delayAfterQuit );
9381             DestroyChildProcess(first.pr, first.useSigterm);
9382         }
9383         first.pr = NoProc;
9384     }
9385     if (second.reuse) {
9386         /* Put second chess program into idle state */
9387         if (second.pr != NoProc &&
9388             gameMode == TwoMachinesPlay) {
9389             SendToProgram("force\n", &second);
9390             if (second.usePing) {
9391               char buf[MSG_SIZ];
9392               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9393               SendToProgram(buf, &second);
9394             }
9395         }
9396     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9397         /* Kill off second chess program */
9398         if (second.isr != NULL)
9399           RemoveInputSource(second.isr);
9400         second.isr = NULL;
9401
9402         if (second.pr != NoProc) {
9403             DoSleep( appData.delayBeforeQuit );
9404             SendToProgram("quit\n", &second);
9405             DoSleep( appData.delayAfterQuit );
9406             DestroyChildProcess(second.pr, second.useSigterm);
9407         }
9408         second.pr = NoProc;
9409     }
9410
9411     if (matchMode && gameMode == TwoMachinesPlay) {
9412         switch (result) {
9413         case WhiteWins:
9414           if (first.twoMachinesColor[0] == 'w') {
9415             first.matchWins++;
9416           } else {
9417             second.matchWins++;
9418           }
9419           break;
9420         case BlackWins:
9421           if (first.twoMachinesColor[0] == 'b') {
9422             first.matchWins++;
9423           } else {
9424             second.matchWins++;
9425           }
9426           break;
9427         default:
9428           break;
9429         }
9430         if (matchGame < appData.matchGames) {
9431             char *tmp;
9432             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9433                 tmp = first.twoMachinesColor;
9434                 first.twoMachinesColor = second.twoMachinesColor;
9435                 second.twoMachinesColor = tmp;
9436             }
9437             gameMode = nextGameMode;
9438             matchGame++;
9439             if(appData.matchPause>10000 || appData.matchPause<10)
9440                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9441             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9442             endingGame = 0; /* [HGM] crash */
9443             return;
9444         } else {
9445             gameMode = nextGameMode;
9446             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9447                      first.tidy, second.tidy,
9448                      first.matchWins, second.matchWins,
9449                      appData.matchGames - (first.matchWins + second.matchWins));
9450             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9451             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9452                 first.twoMachinesColor = "black\n";
9453                 second.twoMachinesColor = "white\n";
9454             } else {
9455                 first.twoMachinesColor = "white\n";
9456                 second.twoMachinesColor = "black\n";
9457             }
9458         }
9459     }
9460     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9461         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9462       ExitAnalyzeMode();
9463     gameMode = nextGameMode;
9464     ModeHighlight();
9465     endingGame = 0;  /* [HGM] crash */
9466     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9467       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9468         matchMode = FALSE; appData.matchGames = matchGame = 0;
9469         DisplayNote(buf);
9470       }
9471     }
9472 }
9473
9474 /* Assumes program was just initialized (initString sent).
9475    Leaves program in force mode. */
9476 void
9477 FeedMovesToProgram(cps, upto)
9478      ChessProgramState *cps;
9479      int upto;
9480 {
9481     int i;
9482
9483     if (appData.debugMode)
9484       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9485               startedFromSetupPosition ? "position and " : "",
9486               backwardMostMove, upto, cps->which);
9487     if(currentlyInitializedVariant != gameInfo.variant) {
9488       char buf[MSG_SIZ];
9489         // [HGM] variantswitch: make engine aware of new variant
9490         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9491                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9492         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9493         SendToProgram(buf, cps);
9494         currentlyInitializedVariant = gameInfo.variant;
9495     }
9496     SendToProgram("force\n", cps);
9497     if (startedFromSetupPosition) {
9498         SendBoard(cps, backwardMostMove);
9499     if (appData.debugMode) {
9500         fprintf(debugFP, "feedMoves\n");
9501     }
9502     }
9503     for (i = backwardMostMove; i < upto; i++) {
9504         SendMoveToProgram(i, cps);
9505     }
9506 }
9507
9508
9509 void
9510 ResurrectChessProgram()
9511 {
9512      /* The chess program may have exited.
9513         If so, restart it and feed it all the moves made so far. */
9514
9515     if (appData.noChessProgram || first.pr != NoProc) return;
9516
9517     StartChessProgram(&first);
9518     InitChessProgram(&first, FALSE);
9519     FeedMovesToProgram(&first, currentMove);
9520
9521     if (!first.sendTime) {
9522         /* can't tell gnuchess what its clock should read,
9523            so we bow to its notion. */
9524         ResetClocks();
9525         timeRemaining[0][currentMove] = whiteTimeRemaining;
9526         timeRemaining[1][currentMove] = blackTimeRemaining;
9527     }
9528
9529     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9530                 appData.icsEngineAnalyze) && first.analysisSupport) {
9531       SendToProgram("analyze\n", &first);
9532       first.analyzing = TRUE;
9533     }
9534 }
9535
9536 /*
9537  * Button procedures
9538  */
9539 void
9540 Reset(redraw, init)
9541      int redraw, init;
9542 {
9543     int i;
9544
9545     if (appData.debugMode) {
9546         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9547                 redraw, init, gameMode);
9548     }
9549     CleanupTail(); // [HGM] vari: delete any stored variations
9550     pausing = pauseExamInvalid = FALSE;
9551     startedFromSetupPosition = blackPlaysFirst = FALSE;
9552     firstMove = TRUE;
9553     whiteFlag = blackFlag = FALSE;
9554     userOfferedDraw = FALSE;
9555     hintRequested = bookRequested = FALSE;
9556     first.maybeThinking = FALSE;
9557     second.maybeThinking = FALSE;
9558     first.bookSuspend = FALSE; // [HGM] book
9559     second.bookSuspend = FALSE;
9560     thinkOutput[0] = NULLCHAR;
9561     lastHint[0] = NULLCHAR;
9562     ClearGameInfo(&gameInfo);
9563     gameInfo.variant = StringToVariant(appData.variant);
9564     ics_user_moved = ics_clock_paused = FALSE;
9565     ics_getting_history = H_FALSE;
9566     ics_gamenum = -1;
9567     white_holding[0] = black_holding[0] = NULLCHAR;
9568     ClearProgramStats();
9569     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9570
9571     ResetFrontEnd();
9572     ClearHighlights();
9573     flipView = appData.flipView;
9574     ClearPremoveHighlights();
9575     gotPremove = FALSE;
9576     alarmSounded = FALSE;
9577
9578     GameEnds(EndOfFile, NULL, GE_PLAYER);
9579     if(appData.serverMovesName != NULL) {
9580         /* [HGM] prepare to make moves file for broadcasting */
9581         clock_t t = clock();
9582         if(serverMoves != NULL) fclose(serverMoves);
9583         serverMoves = fopen(appData.serverMovesName, "r");
9584         if(serverMoves != NULL) {
9585             fclose(serverMoves);
9586             /* delay 15 sec before overwriting, so all clients can see end */
9587             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9588         }
9589         serverMoves = fopen(appData.serverMovesName, "w");
9590     }
9591
9592     ExitAnalyzeMode();
9593     gameMode = BeginningOfGame;
9594     ModeHighlight();
9595     if(appData.icsActive) gameInfo.variant = VariantNormal;
9596     currentMove = forwardMostMove = backwardMostMove = 0;
9597     InitPosition(redraw);
9598     for (i = 0; i < MAX_MOVES; i++) {
9599         if (commentList[i] != NULL) {
9600             free(commentList[i]);
9601             commentList[i] = NULL;
9602         }
9603     }
9604     ResetClocks();
9605     timeRemaining[0][0] = whiteTimeRemaining;
9606     timeRemaining[1][0] = blackTimeRemaining;
9607     if (first.pr == NULL) {
9608         StartChessProgram(&first);
9609     }
9610     if (init) {
9611             InitChessProgram(&first, startedFromSetupPosition);
9612     }
9613     DisplayTitle("");
9614     DisplayMessage("", "");
9615     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9616     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9617 }
9618
9619 void
9620 AutoPlayGameLoop()
9621 {
9622     for (;;) {
9623         if (!AutoPlayOneMove())
9624           return;
9625         if (matchMode || appData.timeDelay == 0)
9626           continue;
9627         if (appData.timeDelay < 0)
9628           return;
9629         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9630         break;
9631     }
9632 }
9633
9634
9635 int
9636 AutoPlayOneMove()
9637 {
9638     int fromX, fromY, toX, toY;
9639
9640     if (appData.debugMode) {
9641       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9642     }
9643
9644     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9645       return FALSE;
9646
9647     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9648       pvInfoList[currentMove].depth = programStats.depth;
9649       pvInfoList[currentMove].score = programStats.score;
9650       pvInfoList[currentMove].time  = 0;
9651       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9652     }
9653
9654     if (currentMove >= forwardMostMove) {
9655       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9656       gameMode = EditGame;
9657       ModeHighlight();
9658
9659       /* [AS] Clear current move marker at the end of a game */
9660       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9661
9662       return FALSE;
9663     }
9664
9665     toX = moveList[currentMove][2] - AAA;
9666     toY = moveList[currentMove][3] - ONE;
9667
9668     if (moveList[currentMove][1] == '@') {
9669         if (appData.highlightLastMove) {
9670             SetHighlights(-1, -1, toX, toY);
9671         }
9672     } else {
9673         fromX = moveList[currentMove][0] - AAA;
9674         fromY = moveList[currentMove][1] - ONE;
9675
9676         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9677
9678         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9679
9680         if (appData.highlightLastMove) {
9681             SetHighlights(fromX, fromY, toX, toY);
9682         }
9683     }
9684     DisplayMove(currentMove);
9685     SendMoveToProgram(currentMove++, &first);
9686     DisplayBothClocks();
9687     DrawPosition(FALSE, boards[currentMove]);
9688     // [HGM] PV info: always display, routine tests if empty
9689     DisplayComment(currentMove - 1, commentList[currentMove]);
9690     return TRUE;
9691 }
9692
9693
9694 int
9695 LoadGameOneMove(readAhead)
9696      ChessMove readAhead;
9697 {
9698     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9699     char promoChar = NULLCHAR;
9700     ChessMove moveType;
9701     char move[MSG_SIZ];
9702     char *p, *q;
9703
9704     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9705         gameMode != AnalyzeMode && gameMode != Training) {
9706         gameFileFP = NULL;
9707         return FALSE;
9708     }
9709
9710     yyboardindex = forwardMostMove;
9711     if (readAhead != EndOfFile) {
9712       moveType = readAhead;
9713     } else {
9714       if (gameFileFP == NULL)
9715           return FALSE;
9716       moveType = (ChessMove) Myylex();
9717     }
9718
9719     done = FALSE;
9720     switch (moveType) {
9721       case Comment:
9722         if (appData.debugMode)
9723           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9724         p = yy_text;
9725
9726         /* append the comment but don't display it */
9727         AppendComment(currentMove, p, FALSE);
9728         return TRUE;
9729
9730       case WhiteCapturesEnPassant:
9731       case BlackCapturesEnPassant:
9732       case WhitePromotion:
9733       case BlackPromotion:
9734       case WhiteNonPromotion:
9735       case BlackNonPromotion:
9736       case NormalMove:
9737       case WhiteKingSideCastle:
9738       case WhiteQueenSideCastle:
9739       case BlackKingSideCastle:
9740       case BlackQueenSideCastle:
9741       case WhiteKingSideCastleWild:
9742       case WhiteQueenSideCastleWild:
9743       case BlackKingSideCastleWild:
9744       case BlackQueenSideCastleWild:
9745       /* PUSH Fabien */
9746       case WhiteHSideCastleFR:
9747       case WhiteASideCastleFR:
9748       case BlackHSideCastleFR:
9749       case BlackASideCastleFR:
9750       /* POP Fabien */
9751         if (appData.debugMode)
9752           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9753         fromX = currentMoveString[0] - AAA;
9754         fromY = currentMoveString[1] - ONE;
9755         toX = currentMoveString[2] - AAA;
9756         toY = currentMoveString[3] - ONE;
9757         promoChar = currentMoveString[4];
9758         break;
9759
9760       case WhiteDrop:
9761       case BlackDrop:
9762         if (appData.debugMode)
9763           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9764         fromX = moveType == WhiteDrop ?
9765           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9766         (int) CharToPiece(ToLower(currentMoveString[0]));
9767         fromY = DROP_RANK;
9768         toX = currentMoveString[2] - AAA;
9769         toY = currentMoveString[3] - ONE;
9770         break;
9771
9772       case WhiteWins:
9773       case BlackWins:
9774       case GameIsDrawn:
9775       case GameUnfinished:
9776         if (appData.debugMode)
9777           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9778         p = strchr(yy_text, '{');
9779         if (p == NULL) p = strchr(yy_text, '(');
9780         if (p == NULL) {
9781             p = yy_text;
9782             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9783         } else {
9784             q = strchr(p, *p == '{' ? '}' : ')');
9785             if (q != NULL) *q = NULLCHAR;
9786             p++;
9787         }
9788         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9789         GameEnds(moveType, p, GE_FILE);
9790         done = TRUE;
9791         if (cmailMsgLoaded) {
9792             ClearHighlights();
9793             flipView = WhiteOnMove(currentMove);
9794             if (moveType == GameUnfinished) flipView = !flipView;
9795             if (appData.debugMode)
9796               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9797         }
9798         break;
9799
9800       case EndOfFile:
9801         if (appData.debugMode)
9802           fprintf(debugFP, "Parser hit end of file\n");
9803         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9804           case MT_NONE:
9805           case MT_CHECK:
9806             break;
9807           case MT_CHECKMATE:
9808           case MT_STAINMATE:
9809             if (WhiteOnMove(currentMove)) {
9810                 GameEnds(BlackWins, "Black mates", GE_FILE);
9811             } else {
9812                 GameEnds(WhiteWins, "White mates", GE_FILE);
9813             }
9814             break;
9815           case MT_STALEMATE:
9816             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9817             break;
9818         }
9819         done = TRUE;
9820         break;
9821
9822       case MoveNumberOne:
9823         if (lastLoadGameStart == GNUChessGame) {
9824             /* GNUChessGames have numbers, but they aren't move numbers */
9825             if (appData.debugMode)
9826               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9827                       yy_text, (int) moveType);
9828             return LoadGameOneMove(EndOfFile); /* tail recursion */
9829         }
9830         /* else fall thru */
9831
9832       case XBoardGame:
9833       case GNUChessGame:
9834       case PGNTag:
9835         /* Reached start of next game in file */
9836         if (appData.debugMode)
9837           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9838         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9839           case MT_NONE:
9840           case MT_CHECK:
9841             break;
9842           case MT_CHECKMATE:
9843           case MT_STAINMATE:
9844             if (WhiteOnMove(currentMove)) {
9845                 GameEnds(BlackWins, "Black mates", GE_FILE);
9846             } else {
9847                 GameEnds(WhiteWins, "White mates", GE_FILE);
9848             }
9849             break;
9850           case MT_STALEMATE:
9851             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9852             break;
9853         }
9854         done = TRUE;
9855         break;
9856
9857       case PositionDiagram:     /* should not happen; ignore */
9858       case ElapsedTime:         /* ignore */
9859       case NAG:                 /* ignore */
9860         if (appData.debugMode)
9861           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9862                   yy_text, (int) moveType);
9863         return LoadGameOneMove(EndOfFile); /* tail recursion */
9864
9865       case IllegalMove:
9866         if (appData.testLegality) {
9867             if (appData.debugMode)
9868               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9869             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9870                     (forwardMostMove / 2) + 1,
9871                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9872             DisplayError(move, 0);
9873             done = TRUE;
9874         } else {
9875             if (appData.debugMode)
9876               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9877                       yy_text, currentMoveString);
9878             fromX = currentMoveString[0] - AAA;
9879             fromY = currentMoveString[1] - ONE;
9880             toX = currentMoveString[2] - AAA;
9881             toY = currentMoveString[3] - ONE;
9882             promoChar = currentMoveString[4];
9883         }
9884         break;
9885
9886       case AmbiguousMove:
9887         if (appData.debugMode)
9888           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9889         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9890                 (forwardMostMove / 2) + 1,
9891                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9892         DisplayError(move, 0);
9893         done = TRUE;
9894         break;
9895
9896       default:
9897       case ImpossibleMove:
9898         if (appData.debugMode)
9899           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9900         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9901                 (forwardMostMove / 2) + 1,
9902                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9903         DisplayError(move, 0);
9904         done = TRUE;
9905         break;
9906     }
9907
9908     if (done) {
9909         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9910             DrawPosition(FALSE, boards[currentMove]);
9911             DisplayBothClocks();
9912             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9913               DisplayComment(currentMove - 1, commentList[currentMove]);
9914         }
9915         (void) StopLoadGameTimer();
9916         gameFileFP = NULL;
9917         cmailOldMove = forwardMostMove;
9918         return FALSE;
9919     } else {
9920         /* currentMoveString is set as a side-effect of yylex */
9921
9922         thinkOutput[0] = NULLCHAR;
9923         MakeMove(fromX, fromY, toX, toY, promoChar);
9924         currentMove = forwardMostMove;
9925         return TRUE;
9926     }
9927 }
9928
9929 /* Load the nth game from the given file */
9930 int
9931 LoadGameFromFile(filename, n, title, useList)
9932      char *filename;
9933      int n;
9934      char *title;
9935      /*Boolean*/ int useList;
9936 {
9937     FILE *f;
9938     char buf[MSG_SIZ];
9939
9940     if (strcmp(filename, "-") == 0) {
9941         f = stdin;
9942         title = "stdin";
9943     } else {
9944         f = fopen(filename, "rb");
9945         if (f == NULL) {
9946           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9947             DisplayError(buf, errno);
9948             return FALSE;
9949         }
9950     }
9951     if (fseek(f, 0, 0) == -1) {
9952         /* f is not seekable; probably a pipe */
9953         useList = FALSE;
9954     }
9955     if (useList && n == 0) {
9956         int error = GameListBuild(f);
9957         if (error) {
9958             DisplayError(_("Cannot build game list"), error);
9959         } else if (!ListEmpty(&gameList) &&
9960                    ((ListGame *) gameList.tailPred)->number > 1) {
9961             GameListPopUp(f, title);
9962             return TRUE;
9963         }
9964         GameListDestroy();
9965         n = 1;
9966     }
9967     if (n == 0) n = 1;
9968     return LoadGame(f, n, title, FALSE);
9969 }
9970
9971
9972 void
9973 MakeRegisteredMove()
9974 {
9975     int fromX, fromY, toX, toY;
9976     char promoChar;
9977     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9978         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9979           case CMAIL_MOVE:
9980           case CMAIL_DRAW:
9981             if (appData.debugMode)
9982               fprintf(debugFP, "Restoring %s for game %d\n",
9983                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9984
9985             thinkOutput[0] = NULLCHAR;
9986             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9987             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9988             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9989             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9990             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9991             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9992             MakeMove(fromX, fromY, toX, toY, promoChar);
9993             ShowMove(fromX, fromY, toX, toY);
9994
9995             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9996               case MT_NONE:
9997               case MT_CHECK:
9998                 break;
9999
10000               case MT_CHECKMATE:
10001               case MT_STAINMATE:
10002                 if (WhiteOnMove(currentMove)) {
10003                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10004                 } else {
10005                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10006                 }
10007                 break;
10008
10009               case MT_STALEMATE:
10010                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10011                 break;
10012             }
10013
10014             break;
10015
10016           case CMAIL_RESIGN:
10017             if (WhiteOnMove(currentMove)) {
10018                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10019             } else {
10020                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10021             }
10022             break;
10023
10024           case CMAIL_ACCEPT:
10025             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10026             break;
10027
10028           default:
10029             break;
10030         }
10031     }
10032
10033     return;
10034 }
10035
10036 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10037 int
10038 CmailLoadGame(f, gameNumber, title, useList)
10039      FILE *f;
10040      int gameNumber;
10041      char *title;
10042      int useList;
10043 {
10044     int retVal;
10045
10046     if (gameNumber > nCmailGames) {
10047         DisplayError(_("No more games in this message"), 0);
10048         return FALSE;
10049     }
10050     if (f == lastLoadGameFP) {
10051         int offset = gameNumber - lastLoadGameNumber;
10052         if (offset == 0) {
10053             cmailMsg[0] = NULLCHAR;
10054             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10055                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10056                 nCmailMovesRegistered--;
10057             }
10058             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10059             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10060                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10061             }
10062         } else {
10063             if (! RegisterMove()) return FALSE;
10064         }
10065     }
10066
10067     retVal = LoadGame(f, gameNumber, title, useList);
10068
10069     /* Make move registered during previous look at this game, if any */
10070     MakeRegisteredMove();
10071
10072     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10073         commentList[currentMove]
10074           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10075         DisplayComment(currentMove - 1, commentList[currentMove]);
10076     }
10077
10078     return retVal;
10079 }
10080
10081 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10082 int
10083 ReloadGame(offset)
10084      int offset;
10085 {
10086     int gameNumber = lastLoadGameNumber + offset;
10087     if (lastLoadGameFP == NULL) {
10088         DisplayError(_("No game has been loaded yet"), 0);
10089         return FALSE;
10090     }
10091     if (gameNumber <= 0) {
10092         DisplayError(_("Can't back up any further"), 0);
10093         return FALSE;
10094     }
10095     if (cmailMsgLoaded) {
10096         return CmailLoadGame(lastLoadGameFP, gameNumber,
10097                              lastLoadGameTitle, lastLoadGameUseList);
10098     } else {
10099         return LoadGame(lastLoadGameFP, gameNumber,
10100                         lastLoadGameTitle, lastLoadGameUseList);
10101     }
10102 }
10103
10104
10105
10106 /* Load the nth game from open file f */
10107 int
10108 LoadGame(f, gameNumber, title, useList)
10109      FILE *f;
10110      int gameNumber;
10111      char *title;
10112      int useList;
10113 {
10114     ChessMove cm;
10115     char buf[MSG_SIZ];
10116     int gn = gameNumber;
10117     ListGame *lg = NULL;
10118     int numPGNTags = 0;
10119     int err;
10120     GameMode oldGameMode;
10121     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10122
10123     if (appData.debugMode)
10124         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10125
10126     if (gameMode == Training )
10127         SetTrainingModeOff();
10128
10129     oldGameMode = gameMode;
10130     if (gameMode != BeginningOfGame) {
10131       Reset(FALSE, TRUE);
10132     }
10133
10134     gameFileFP = f;
10135     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10136         fclose(lastLoadGameFP);
10137     }
10138
10139     if (useList) {
10140         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10141
10142         if (lg) {
10143             fseek(f, lg->offset, 0);
10144             GameListHighlight(gameNumber);
10145             gn = 1;
10146         }
10147         else {
10148             DisplayError(_("Game number out of range"), 0);
10149             return FALSE;
10150         }
10151     } else {
10152         GameListDestroy();
10153         if (fseek(f, 0, 0) == -1) {
10154             if (f == lastLoadGameFP ?
10155                 gameNumber == lastLoadGameNumber + 1 :
10156                 gameNumber == 1) {
10157                 gn = 1;
10158             } else {
10159                 DisplayError(_("Can't seek on game file"), 0);
10160                 return FALSE;
10161             }
10162         }
10163     }
10164     lastLoadGameFP = f;
10165     lastLoadGameNumber = gameNumber;
10166     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10167     lastLoadGameUseList = useList;
10168
10169     yynewfile(f);
10170
10171     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10172       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10173                 lg->gameInfo.black);
10174             DisplayTitle(buf);
10175     } else if (*title != NULLCHAR) {
10176         if (gameNumber > 1) {
10177           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10178             DisplayTitle(buf);
10179         } else {
10180             DisplayTitle(title);
10181         }
10182     }
10183
10184     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10185         gameMode = PlayFromGameFile;
10186         ModeHighlight();
10187     }
10188
10189     currentMove = forwardMostMove = backwardMostMove = 0;
10190     CopyBoard(boards[0], initialPosition);
10191     StopClocks();
10192
10193     /*
10194      * Skip the first gn-1 games in the file.
10195      * Also skip over anything that precedes an identifiable
10196      * start of game marker, to avoid being confused by
10197      * garbage at the start of the file.  Currently
10198      * recognized start of game markers are the move number "1",
10199      * the pattern "gnuchess .* game", the pattern
10200      * "^[#;%] [^ ]* game file", and a PGN tag block.
10201      * A game that starts with one of the latter two patterns
10202      * will also have a move number 1, possibly
10203      * following a position diagram.
10204      * 5-4-02: Let's try being more lenient and allowing a game to
10205      * start with an unnumbered move.  Does that break anything?
10206      */
10207     cm = lastLoadGameStart = EndOfFile;
10208     while (gn > 0) {
10209         yyboardindex = forwardMostMove;
10210         cm = (ChessMove) Myylex();
10211         switch (cm) {
10212           case EndOfFile:
10213             if (cmailMsgLoaded) {
10214                 nCmailGames = CMAIL_MAX_GAMES - gn;
10215             } else {
10216                 Reset(TRUE, TRUE);
10217                 DisplayError(_("Game not found in file"), 0);
10218             }
10219             return FALSE;
10220
10221           case GNUChessGame:
10222           case XBoardGame:
10223             gn--;
10224             lastLoadGameStart = cm;
10225             break;
10226
10227           case MoveNumberOne:
10228             switch (lastLoadGameStart) {
10229               case GNUChessGame:
10230               case XBoardGame:
10231               case PGNTag:
10232                 break;
10233               case MoveNumberOne:
10234               case EndOfFile:
10235                 gn--;           /* count this game */
10236                 lastLoadGameStart = cm;
10237                 break;
10238               default:
10239                 /* impossible */
10240                 break;
10241             }
10242             break;
10243
10244           case PGNTag:
10245             switch (lastLoadGameStart) {
10246               case GNUChessGame:
10247               case PGNTag:
10248               case MoveNumberOne:
10249               case EndOfFile:
10250                 gn--;           /* count this game */
10251                 lastLoadGameStart = cm;
10252                 break;
10253               case XBoardGame:
10254                 lastLoadGameStart = cm; /* game counted already */
10255                 break;
10256               default:
10257                 /* impossible */
10258                 break;
10259             }
10260             if (gn > 0) {
10261                 do {
10262                     yyboardindex = forwardMostMove;
10263                     cm = (ChessMove) Myylex();
10264                 } while (cm == PGNTag || cm == Comment);
10265             }
10266             break;
10267
10268           case WhiteWins:
10269           case BlackWins:
10270           case GameIsDrawn:
10271             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10272                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10273                     != CMAIL_OLD_RESULT) {
10274                     nCmailResults ++ ;
10275                     cmailResult[  CMAIL_MAX_GAMES
10276                                 - gn - 1] = CMAIL_OLD_RESULT;
10277                 }
10278             }
10279             break;
10280
10281           case NormalMove:
10282             /* Only a NormalMove can be at the start of a game
10283              * without a position diagram. */
10284             if (lastLoadGameStart == EndOfFile ) {
10285               gn--;
10286               lastLoadGameStart = MoveNumberOne;
10287             }
10288             break;
10289
10290           default:
10291             break;
10292         }
10293     }
10294
10295     if (appData.debugMode)
10296       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10297
10298     if (cm == XBoardGame) {
10299         /* Skip any header junk before position diagram and/or move 1 */
10300         for (;;) {
10301             yyboardindex = forwardMostMove;
10302             cm = (ChessMove) Myylex();
10303
10304             if (cm == EndOfFile ||
10305                 cm == GNUChessGame || cm == XBoardGame) {
10306                 /* Empty game; pretend end-of-file and handle later */
10307                 cm = EndOfFile;
10308                 break;
10309             }
10310
10311             if (cm == MoveNumberOne || cm == PositionDiagram ||
10312                 cm == PGNTag || cm == Comment)
10313               break;
10314         }
10315     } else if (cm == GNUChessGame) {
10316         if (gameInfo.event != NULL) {
10317             free(gameInfo.event);
10318         }
10319         gameInfo.event = StrSave(yy_text);
10320     }
10321
10322     startedFromSetupPosition = FALSE;
10323     while (cm == PGNTag) {
10324         if (appData.debugMode)
10325           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10326         err = ParsePGNTag(yy_text, &gameInfo);
10327         if (!err) numPGNTags++;
10328
10329         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10330         if(gameInfo.variant != oldVariant) {
10331             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10332             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10333             InitPosition(TRUE);
10334             oldVariant = gameInfo.variant;
10335             if (appData.debugMode)
10336               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10337         }
10338
10339
10340         if (gameInfo.fen != NULL) {
10341           Board initial_position;
10342           startedFromSetupPosition = TRUE;
10343           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10344             Reset(TRUE, TRUE);
10345             DisplayError(_("Bad FEN position in file"), 0);
10346             return FALSE;
10347           }
10348           CopyBoard(boards[0], initial_position);
10349           if (blackPlaysFirst) {
10350             currentMove = forwardMostMove = backwardMostMove = 1;
10351             CopyBoard(boards[1], initial_position);
10352             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10353             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10354             timeRemaining[0][1] = whiteTimeRemaining;
10355             timeRemaining[1][1] = blackTimeRemaining;
10356             if (commentList[0] != NULL) {
10357               commentList[1] = commentList[0];
10358               commentList[0] = NULL;
10359             }
10360           } else {
10361             currentMove = forwardMostMove = backwardMostMove = 0;
10362           }
10363           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10364           {   int i;
10365               initialRulePlies = FENrulePlies;
10366               for( i=0; i< nrCastlingRights; i++ )
10367                   initialRights[i] = initial_position[CASTLING][i];
10368           }
10369           yyboardindex = forwardMostMove;
10370           free(gameInfo.fen);
10371           gameInfo.fen = NULL;
10372         }
10373
10374         yyboardindex = forwardMostMove;
10375         cm = (ChessMove) Myylex();
10376
10377         /* Handle comments interspersed among the tags */
10378         while (cm == Comment) {
10379             char *p;
10380             if (appData.debugMode)
10381               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10382             p = yy_text;
10383             AppendComment(currentMove, p, FALSE);
10384             yyboardindex = forwardMostMove;
10385             cm = (ChessMove) Myylex();
10386         }
10387     }
10388
10389     /* don't rely on existence of Event tag since if game was
10390      * pasted from clipboard the Event tag may not exist
10391      */
10392     if (numPGNTags > 0){
10393         char *tags;
10394         if (gameInfo.variant == VariantNormal) {
10395           VariantClass v = StringToVariant(gameInfo.event);
10396           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10397           if(v < VariantShogi) gameInfo.variant = v;
10398         }
10399         if (!matchMode) {
10400           if( appData.autoDisplayTags ) {
10401             tags = PGNTags(&gameInfo);
10402             TagsPopUp(tags, CmailMsg());
10403             free(tags);
10404           }
10405         }
10406     } else {
10407         /* Make something up, but don't display it now */
10408         SetGameInfo();
10409         TagsPopDown();
10410     }
10411
10412     if (cm == PositionDiagram) {
10413         int i, j;
10414         char *p;
10415         Board initial_position;
10416
10417         if (appData.debugMode)
10418           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10419
10420         if (!startedFromSetupPosition) {
10421             p = yy_text;
10422             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10423               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10424                 switch (*p) {
10425                   case '{':
10426                   case '[':
10427                   case '-':
10428                   case ' ':
10429                   case '\t':
10430                   case '\n':
10431                   case '\r':
10432                     break;
10433                   default:
10434                     initial_position[i][j++] = CharToPiece(*p);
10435                     break;
10436                 }
10437             while (*p == ' ' || *p == '\t' ||
10438                    *p == '\n' || *p == '\r') p++;
10439
10440             if (strncmp(p, "black", strlen("black"))==0)
10441               blackPlaysFirst = TRUE;
10442             else
10443               blackPlaysFirst = FALSE;
10444             startedFromSetupPosition = TRUE;
10445
10446             CopyBoard(boards[0], initial_position);
10447             if (blackPlaysFirst) {
10448                 currentMove = forwardMostMove = backwardMostMove = 1;
10449                 CopyBoard(boards[1], initial_position);
10450                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10451                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10452                 timeRemaining[0][1] = whiteTimeRemaining;
10453                 timeRemaining[1][1] = blackTimeRemaining;
10454                 if (commentList[0] != NULL) {
10455                     commentList[1] = commentList[0];
10456                     commentList[0] = NULL;
10457                 }
10458             } else {
10459                 currentMove = forwardMostMove = backwardMostMove = 0;
10460             }
10461         }
10462         yyboardindex = forwardMostMove;
10463         cm = (ChessMove) Myylex();
10464     }
10465
10466     if (first.pr == NoProc) {
10467         StartChessProgram(&first);
10468     }
10469     InitChessProgram(&first, FALSE);
10470     SendToProgram("force\n", &first);
10471     if (startedFromSetupPosition) {
10472         SendBoard(&first, forwardMostMove);
10473     if (appData.debugMode) {
10474         fprintf(debugFP, "Load Game\n");
10475     }
10476         DisplayBothClocks();
10477     }
10478
10479     /* [HGM] server: flag to write setup moves in broadcast file as one */
10480     loadFlag = appData.suppressLoadMoves;
10481
10482     while (cm == Comment) {
10483         char *p;
10484         if (appData.debugMode)
10485           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10486         p = yy_text;
10487         AppendComment(currentMove, p, FALSE);
10488         yyboardindex = forwardMostMove;
10489         cm = (ChessMove) Myylex();
10490     }
10491
10492     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10493         cm == WhiteWins || cm == BlackWins ||
10494         cm == GameIsDrawn || cm == GameUnfinished) {
10495         DisplayMessage("", _("No moves in game"));
10496         if (cmailMsgLoaded) {
10497             if (appData.debugMode)
10498               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10499             ClearHighlights();
10500             flipView = FALSE;
10501         }
10502         DrawPosition(FALSE, boards[currentMove]);
10503         DisplayBothClocks();
10504         gameMode = EditGame;
10505         ModeHighlight();
10506         gameFileFP = NULL;
10507         cmailOldMove = 0;
10508         return TRUE;
10509     }
10510
10511     // [HGM] PV info: routine tests if comment empty
10512     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10513         DisplayComment(currentMove - 1, commentList[currentMove]);
10514     }
10515     if (!matchMode && appData.timeDelay != 0)
10516       DrawPosition(FALSE, boards[currentMove]);
10517
10518     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10519       programStats.ok_to_send = 1;
10520     }
10521
10522     /* if the first token after the PGN tags is a move
10523      * and not move number 1, retrieve it from the parser
10524      */
10525     if (cm != MoveNumberOne)
10526         LoadGameOneMove(cm);
10527
10528     /* load the remaining moves from the file */
10529     while (LoadGameOneMove(EndOfFile)) {
10530       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10531       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10532     }
10533
10534     /* rewind to the start of the game */
10535     currentMove = backwardMostMove;
10536
10537     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10538
10539     if (oldGameMode == AnalyzeFile ||
10540         oldGameMode == AnalyzeMode) {
10541       AnalyzeFileEvent();
10542     }
10543
10544     if (matchMode || appData.timeDelay == 0) {
10545       ToEndEvent();
10546       gameMode = EditGame;
10547       ModeHighlight();
10548     } else if (appData.timeDelay > 0) {
10549       AutoPlayGameLoop();
10550     }
10551
10552     if (appData.debugMode)
10553         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10554
10555     loadFlag = 0; /* [HGM] true game starts */
10556     return TRUE;
10557 }
10558
10559 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10560 int
10561 ReloadPosition(offset)
10562      int offset;
10563 {
10564     int positionNumber = lastLoadPositionNumber + offset;
10565     if (lastLoadPositionFP == NULL) {
10566         DisplayError(_("No position has been loaded yet"), 0);
10567         return FALSE;
10568     }
10569     if (positionNumber <= 0) {
10570         DisplayError(_("Can't back up any further"), 0);
10571         return FALSE;
10572     }
10573     return LoadPosition(lastLoadPositionFP, positionNumber,
10574                         lastLoadPositionTitle);
10575 }
10576
10577 /* Load the nth position from the given file */
10578 int
10579 LoadPositionFromFile(filename, n, title)
10580      char *filename;
10581      int n;
10582      char *title;
10583 {
10584     FILE *f;
10585     char buf[MSG_SIZ];
10586
10587     if (strcmp(filename, "-") == 0) {
10588         return LoadPosition(stdin, n, "stdin");
10589     } else {
10590         f = fopen(filename, "rb");
10591         if (f == NULL) {
10592             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10593             DisplayError(buf, errno);
10594             return FALSE;
10595         } else {
10596             return LoadPosition(f, n, title);
10597         }
10598     }
10599 }
10600
10601 /* Load the nth position from the given open file, and close it */
10602 int
10603 LoadPosition(f, positionNumber, title)
10604      FILE *f;
10605      int positionNumber;
10606      char *title;
10607 {
10608     char *p, line[MSG_SIZ];
10609     Board initial_position;
10610     int i, j, fenMode, pn;
10611
10612     if (gameMode == Training )
10613         SetTrainingModeOff();
10614
10615     if (gameMode != BeginningOfGame) {
10616         Reset(FALSE, TRUE);
10617     }
10618     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10619         fclose(lastLoadPositionFP);
10620     }
10621     if (positionNumber == 0) positionNumber = 1;
10622     lastLoadPositionFP = f;
10623     lastLoadPositionNumber = positionNumber;
10624     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10625     if (first.pr == NoProc) {
10626       StartChessProgram(&first);
10627       InitChessProgram(&first, FALSE);
10628     }
10629     pn = positionNumber;
10630     if (positionNumber < 0) {
10631         /* Negative position number means to seek to that byte offset */
10632         if (fseek(f, -positionNumber, 0) == -1) {
10633             DisplayError(_("Can't seek on position file"), 0);
10634             return FALSE;
10635         };
10636         pn = 1;
10637     } else {
10638         if (fseek(f, 0, 0) == -1) {
10639             if (f == lastLoadPositionFP ?
10640                 positionNumber == lastLoadPositionNumber + 1 :
10641                 positionNumber == 1) {
10642                 pn = 1;
10643             } else {
10644                 DisplayError(_("Can't seek on position file"), 0);
10645                 return FALSE;
10646             }
10647         }
10648     }
10649     /* See if this file is FEN or old-style xboard */
10650     if (fgets(line, MSG_SIZ, f) == NULL) {
10651         DisplayError(_("Position not found in file"), 0);
10652         return FALSE;
10653     }
10654     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10655     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10656
10657     if (pn >= 2) {
10658         if (fenMode || line[0] == '#') pn--;
10659         while (pn > 0) {
10660             /* skip positions before number pn */
10661             if (fgets(line, MSG_SIZ, f) == NULL) {
10662                 Reset(TRUE, TRUE);
10663                 DisplayError(_("Position not found in file"), 0);
10664                 return FALSE;
10665             }
10666             if (fenMode || line[0] == '#') pn--;
10667         }
10668     }
10669
10670     if (fenMode) {
10671         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10672             DisplayError(_("Bad FEN position in file"), 0);
10673             return FALSE;
10674         }
10675     } else {
10676         (void) fgets(line, MSG_SIZ, f);
10677         (void) fgets(line, MSG_SIZ, f);
10678
10679         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10680             (void) fgets(line, MSG_SIZ, f);
10681             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10682                 if (*p == ' ')
10683                   continue;
10684                 initial_position[i][j++] = CharToPiece(*p);
10685             }
10686         }
10687
10688         blackPlaysFirst = FALSE;
10689         if (!feof(f)) {
10690             (void) fgets(line, MSG_SIZ, f);
10691             if (strncmp(line, "black", strlen("black"))==0)
10692               blackPlaysFirst = TRUE;
10693         }
10694     }
10695     startedFromSetupPosition = TRUE;
10696
10697     SendToProgram("force\n", &first);
10698     CopyBoard(boards[0], initial_position);
10699     if (blackPlaysFirst) {
10700         currentMove = forwardMostMove = backwardMostMove = 1;
10701         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10702         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10703         CopyBoard(boards[1], initial_position);
10704         DisplayMessage("", _("Black to play"));
10705     } else {
10706         currentMove = forwardMostMove = backwardMostMove = 0;
10707         DisplayMessage("", _("White to play"));
10708     }
10709     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10710     SendBoard(&first, forwardMostMove);
10711     if (appData.debugMode) {
10712 int i, j;
10713   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10714   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10715         fprintf(debugFP, "Load Position\n");
10716     }
10717
10718     if (positionNumber > 1) {
10719       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10720         DisplayTitle(line);
10721     } else {
10722         DisplayTitle(title);
10723     }
10724     gameMode = EditGame;
10725     ModeHighlight();
10726     ResetClocks();
10727     timeRemaining[0][1] = whiteTimeRemaining;
10728     timeRemaining[1][1] = blackTimeRemaining;
10729     DrawPosition(FALSE, boards[currentMove]);
10730
10731     return TRUE;
10732 }
10733
10734
10735 void
10736 CopyPlayerNameIntoFileName(dest, src)
10737      char **dest, *src;
10738 {
10739     while (*src != NULLCHAR && *src != ',') {
10740         if (*src == ' ') {
10741             *(*dest)++ = '_';
10742             src++;
10743         } else {
10744             *(*dest)++ = *src++;
10745         }
10746     }
10747 }
10748
10749 char *DefaultFileName(ext)
10750      char *ext;
10751 {
10752     static char def[MSG_SIZ];
10753     char *p;
10754
10755     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10756         p = def;
10757         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10758         *p++ = '-';
10759         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10760         *p++ = '.';
10761         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10762     } else {
10763         def[0] = NULLCHAR;
10764     }
10765     return def;
10766 }
10767
10768 /* Save the current game to the given file */
10769 int
10770 SaveGameToFile(filename, append)
10771      char *filename;
10772      int append;
10773 {
10774     FILE *f;
10775     char buf[MSG_SIZ];
10776
10777     if (strcmp(filename, "-") == 0) {
10778         return SaveGame(stdout, 0, NULL);
10779     } else {
10780         f = fopen(filename, append ? "a" : "w");
10781         if (f == NULL) {
10782             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10783             DisplayError(buf, errno);
10784             return FALSE;
10785         } else {
10786             return SaveGame(f, 0, NULL);
10787         }
10788     }
10789 }
10790
10791 char *
10792 SavePart(str)
10793      char *str;
10794 {
10795     static char buf[MSG_SIZ];
10796     char *p;
10797
10798     p = strchr(str, ' ');
10799     if (p == NULL) return str;
10800     strncpy(buf, str, p - str);
10801     buf[p - str] = NULLCHAR;
10802     return buf;
10803 }
10804
10805 #define PGN_MAX_LINE 75
10806
10807 #define PGN_SIDE_WHITE  0
10808 #define PGN_SIDE_BLACK  1
10809
10810 /* [AS] */
10811 static int FindFirstMoveOutOfBook( int side )
10812 {
10813     int result = -1;
10814
10815     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10816         int index = backwardMostMove;
10817         int has_book_hit = 0;
10818
10819         if( (index % 2) != side ) {
10820             index++;
10821         }
10822
10823         while( index < forwardMostMove ) {
10824             /* Check to see if engine is in book */
10825             int depth = pvInfoList[index].depth;
10826             int score = pvInfoList[index].score;
10827             int in_book = 0;
10828
10829             if( depth <= 2 ) {
10830                 in_book = 1;
10831             }
10832             else if( score == 0 && depth == 63 ) {
10833                 in_book = 1; /* Zappa */
10834             }
10835             else if( score == 2 && depth == 99 ) {
10836                 in_book = 1; /* Abrok */
10837             }
10838
10839             has_book_hit += in_book;
10840
10841             if( ! in_book ) {
10842                 result = index;
10843
10844                 break;
10845             }
10846
10847             index += 2;
10848         }
10849     }
10850
10851     return result;
10852 }
10853
10854 /* [AS] */
10855 void GetOutOfBookInfo( char * buf )
10856 {
10857     int oob[2];
10858     int i;
10859     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10860
10861     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10862     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10863
10864     *buf = '\0';
10865
10866     if( oob[0] >= 0 || oob[1] >= 0 ) {
10867         for( i=0; i<2; i++ ) {
10868             int idx = oob[i];
10869
10870             if( idx >= 0 ) {
10871                 if( i > 0 && oob[0] >= 0 ) {
10872                     strcat( buf, "   " );
10873                 }
10874
10875                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10876                 sprintf( buf+strlen(buf), "%s%.2f",
10877                     pvInfoList[idx].score >= 0 ? "+" : "",
10878                     pvInfoList[idx].score / 100.0 );
10879             }
10880         }
10881     }
10882 }
10883
10884 /* Save game in PGN style and close the file */
10885 int
10886 SaveGamePGN(f)
10887      FILE *f;
10888 {
10889     int i, offset, linelen, newblock;
10890     time_t tm;
10891 //    char *movetext;
10892     char numtext[32];
10893     int movelen, numlen, blank;
10894     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10895
10896     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10897
10898     tm = time((time_t *) NULL);
10899
10900     PrintPGNTags(f, &gameInfo);
10901
10902     if (backwardMostMove > 0 || startedFromSetupPosition) {
10903         char *fen = PositionToFEN(backwardMostMove, NULL);
10904         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10905         fprintf(f, "\n{--------------\n");
10906         PrintPosition(f, backwardMostMove);
10907         fprintf(f, "--------------}\n");
10908         free(fen);
10909     }
10910     else {
10911         /* [AS] Out of book annotation */
10912         if( appData.saveOutOfBookInfo ) {
10913             char buf[64];
10914
10915             GetOutOfBookInfo( buf );
10916
10917             if( buf[0] != '\0' ) {
10918                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10919             }
10920         }
10921
10922         fprintf(f, "\n");
10923     }
10924
10925     i = backwardMostMove;
10926     linelen = 0;
10927     newblock = TRUE;
10928
10929     while (i < forwardMostMove) {
10930         /* Print comments preceding this move */
10931         if (commentList[i] != NULL) {
10932             if (linelen > 0) fprintf(f, "\n");
10933             fprintf(f, "%s", commentList[i]);
10934             linelen = 0;
10935             newblock = TRUE;
10936         }
10937
10938         /* Format move number */
10939         if ((i % 2) == 0)
10940           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10941         else
10942           if (newblock)
10943             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10944           else
10945             numtext[0] = NULLCHAR;
10946
10947         numlen = strlen(numtext);
10948         newblock = FALSE;
10949
10950         /* Print move number */
10951         blank = linelen > 0 && numlen > 0;
10952         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10953             fprintf(f, "\n");
10954             linelen = 0;
10955             blank = 0;
10956         }
10957         if (blank) {
10958             fprintf(f, " ");
10959             linelen++;
10960         }
10961         fprintf(f, "%s", numtext);
10962         linelen += numlen;
10963
10964         /* Get move */
10965         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10966         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10967
10968         /* Print move */
10969         blank = linelen > 0 && movelen > 0;
10970         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10971             fprintf(f, "\n");
10972             linelen = 0;
10973             blank = 0;
10974         }
10975         if (blank) {
10976             fprintf(f, " ");
10977             linelen++;
10978         }
10979         fprintf(f, "%s", move_buffer);
10980         linelen += movelen;
10981
10982         /* [AS] Add PV info if present */
10983         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10984             /* [HGM] add time */
10985             char buf[MSG_SIZ]; int seconds;
10986
10987             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10988
10989             if( seconds <= 0)
10990               buf[0] = 0;
10991             else
10992               if( seconds < 30 )
10993                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10994               else
10995                 {
10996                   seconds = (seconds + 4)/10; // round to full seconds
10997                   if( seconds < 60 )
10998                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10999                   else
11000                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11001                 }
11002
11003             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11004                       pvInfoList[i].score >= 0 ? "+" : "",
11005                       pvInfoList[i].score / 100.0,
11006                       pvInfoList[i].depth,
11007                       buf );
11008
11009             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11010
11011             /* Print score/depth */
11012             blank = linelen > 0 && movelen > 0;
11013             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11014                 fprintf(f, "\n");
11015                 linelen = 0;
11016                 blank = 0;
11017             }
11018             if (blank) {
11019                 fprintf(f, " ");
11020                 linelen++;
11021             }
11022             fprintf(f, "%s", move_buffer);
11023             linelen += movelen;
11024         }
11025
11026         i++;
11027     }
11028
11029     /* Start a new line */
11030     if (linelen > 0) fprintf(f, "\n");
11031
11032     /* Print comments after last move */
11033     if (commentList[i] != NULL) {
11034         fprintf(f, "%s\n", commentList[i]);
11035     }
11036
11037     /* Print result */
11038     if (gameInfo.resultDetails != NULL &&
11039         gameInfo.resultDetails[0] != NULLCHAR) {
11040         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11041                 PGNResult(gameInfo.result));
11042     } else {
11043         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11044     }
11045
11046     fclose(f);
11047     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11048     return TRUE;
11049 }
11050
11051 /* Save game in old style and close the file */
11052 int
11053 SaveGameOldStyle(f)
11054      FILE *f;
11055 {
11056     int i, offset;
11057     time_t tm;
11058
11059     tm = time((time_t *) NULL);
11060
11061     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11062     PrintOpponents(f);
11063
11064     if (backwardMostMove > 0 || startedFromSetupPosition) {
11065         fprintf(f, "\n[--------------\n");
11066         PrintPosition(f, backwardMostMove);
11067         fprintf(f, "--------------]\n");
11068     } else {
11069         fprintf(f, "\n");
11070     }
11071
11072     i = backwardMostMove;
11073     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11074
11075     while (i < forwardMostMove) {
11076         if (commentList[i] != NULL) {
11077             fprintf(f, "[%s]\n", commentList[i]);
11078         }
11079
11080         if ((i % 2) == 1) {
11081             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11082             i++;
11083         } else {
11084             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11085             i++;
11086             if (commentList[i] != NULL) {
11087                 fprintf(f, "\n");
11088                 continue;
11089             }
11090             if (i >= forwardMostMove) {
11091                 fprintf(f, "\n");
11092                 break;
11093             }
11094             fprintf(f, "%s\n", parseList[i]);
11095             i++;
11096         }
11097     }
11098
11099     if (commentList[i] != NULL) {
11100         fprintf(f, "[%s]\n", commentList[i]);
11101     }
11102
11103     /* This isn't really the old style, but it's close enough */
11104     if (gameInfo.resultDetails != NULL &&
11105         gameInfo.resultDetails[0] != NULLCHAR) {
11106         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11107                 gameInfo.resultDetails);
11108     } else {
11109         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11110     }
11111
11112     fclose(f);
11113     return TRUE;
11114 }
11115
11116 /* Save the current game to open file f and close the file */
11117 int
11118 SaveGame(f, dummy, dummy2)
11119      FILE *f;
11120      int dummy;
11121      char *dummy2;
11122 {
11123     if (gameMode == EditPosition) EditPositionDone(TRUE);
11124     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11125     if (appData.oldSaveStyle)
11126       return SaveGameOldStyle(f);
11127     else
11128       return SaveGamePGN(f);
11129 }
11130
11131 /* Save the current position to the given file */
11132 int
11133 SavePositionToFile(filename)
11134      char *filename;
11135 {
11136     FILE *f;
11137     char buf[MSG_SIZ];
11138
11139     if (strcmp(filename, "-") == 0) {
11140         return SavePosition(stdout, 0, NULL);
11141     } else {
11142         f = fopen(filename, "a");
11143         if (f == NULL) {
11144             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11145             DisplayError(buf, errno);
11146             return FALSE;
11147         } else {
11148             SavePosition(f, 0, NULL);
11149             return TRUE;
11150         }
11151     }
11152 }
11153
11154 /* Save the current position to the given open file and close the file */
11155 int
11156 SavePosition(f, dummy, dummy2)
11157      FILE *f;
11158      int dummy;
11159      char *dummy2;
11160 {
11161     time_t tm;
11162     char *fen;
11163
11164     if (gameMode == EditPosition) EditPositionDone(TRUE);
11165     if (appData.oldSaveStyle) {
11166         tm = time((time_t *) NULL);
11167
11168         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11169         PrintOpponents(f);
11170         fprintf(f, "[--------------\n");
11171         PrintPosition(f, currentMove);
11172         fprintf(f, "--------------]\n");
11173     } else {
11174         fen = PositionToFEN(currentMove, NULL);
11175         fprintf(f, "%s\n", fen);
11176         free(fen);
11177     }
11178     fclose(f);
11179     return TRUE;
11180 }
11181
11182 void
11183 ReloadCmailMsgEvent(unregister)
11184      int unregister;
11185 {
11186 #if !WIN32
11187     static char *inFilename = NULL;
11188     static char *outFilename;
11189     int i;
11190     struct stat inbuf, outbuf;
11191     int status;
11192
11193     /* Any registered moves are unregistered if unregister is set, */
11194     /* i.e. invoked by the signal handler */
11195     if (unregister) {
11196         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11197             cmailMoveRegistered[i] = FALSE;
11198             if (cmailCommentList[i] != NULL) {
11199                 free(cmailCommentList[i]);
11200                 cmailCommentList[i] = NULL;
11201             }
11202         }
11203         nCmailMovesRegistered = 0;
11204     }
11205
11206     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11207         cmailResult[i] = CMAIL_NOT_RESULT;
11208     }
11209     nCmailResults = 0;
11210
11211     if (inFilename == NULL) {
11212         /* Because the filenames are static they only get malloced once  */
11213         /* and they never get freed                                      */
11214         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11215         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11216
11217         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11218         sprintf(outFilename, "%s.out", appData.cmailGameName);
11219     }
11220
11221     status = stat(outFilename, &outbuf);
11222     if (status < 0) {
11223         cmailMailedMove = FALSE;
11224     } else {
11225         status = stat(inFilename, &inbuf);
11226         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11227     }
11228
11229     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11230        counts the games, notes how each one terminated, etc.
11231
11232        It would be nice to remove this kludge and instead gather all
11233        the information while building the game list.  (And to keep it
11234        in the game list nodes instead of having a bunch of fixed-size
11235        parallel arrays.)  Note this will require getting each game's
11236        termination from the PGN tags, as the game list builder does
11237        not process the game moves.  --mann
11238        */
11239     cmailMsgLoaded = TRUE;
11240     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11241
11242     /* Load first game in the file or popup game menu */
11243     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11244
11245 #endif /* !WIN32 */
11246     return;
11247 }
11248
11249 int
11250 RegisterMove()
11251 {
11252     FILE *f;
11253     char string[MSG_SIZ];
11254
11255     if (   cmailMailedMove
11256         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11257         return TRUE;            /* Allow free viewing  */
11258     }
11259
11260     /* Unregister move to ensure that we don't leave RegisterMove        */
11261     /* with the move registered when the conditions for registering no   */
11262     /* longer hold                                                       */
11263     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11264         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11265         nCmailMovesRegistered --;
11266
11267         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11268           {
11269               free(cmailCommentList[lastLoadGameNumber - 1]);
11270               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11271           }
11272     }
11273
11274     if (cmailOldMove == -1) {
11275         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11276         return FALSE;
11277     }
11278
11279     if (currentMove > cmailOldMove + 1) {
11280         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11281         return FALSE;
11282     }
11283
11284     if (currentMove < cmailOldMove) {
11285         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11286         return FALSE;
11287     }
11288
11289     if (forwardMostMove > currentMove) {
11290         /* Silently truncate extra moves */
11291         TruncateGame();
11292     }
11293
11294     if (   (currentMove == cmailOldMove + 1)
11295         || (   (currentMove == cmailOldMove)
11296             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11297                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11298         if (gameInfo.result != GameUnfinished) {
11299             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11300         }
11301
11302         if (commentList[currentMove] != NULL) {
11303             cmailCommentList[lastLoadGameNumber - 1]
11304               = StrSave(commentList[currentMove]);
11305         }
11306         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11307
11308         if (appData.debugMode)
11309           fprintf(debugFP, "Saving %s for game %d\n",
11310                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11311
11312         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11313
11314         f = fopen(string, "w");
11315         if (appData.oldSaveStyle) {
11316             SaveGameOldStyle(f); /* also closes the file */
11317
11318             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11319             f = fopen(string, "w");
11320             SavePosition(f, 0, NULL); /* also closes the file */
11321         } else {
11322             fprintf(f, "{--------------\n");
11323             PrintPosition(f, currentMove);
11324             fprintf(f, "--------------}\n\n");
11325
11326             SaveGame(f, 0, NULL); /* also closes the file*/
11327         }
11328
11329         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11330         nCmailMovesRegistered ++;
11331     } else if (nCmailGames == 1) {
11332         DisplayError(_("You have not made a move yet"), 0);
11333         return FALSE;
11334     }
11335
11336     return TRUE;
11337 }
11338
11339 void
11340 MailMoveEvent()
11341 {
11342 #if !WIN32
11343     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11344     FILE *commandOutput;
11345     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11346     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11347     int nBuffers;
11348     int i;
11349     int archived;
11350     char *arcDir;
11351
11352     if (! cmailMsgLoaded) {
11353         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11354         return;
11355     }
11356
11357     if (nCmailGames == nCmailResults) {
11358         DisplayError(_("No unfinished games"), 0);
11359         return;
11360     }
11361
11362 #if CMAIL_PROHIBIT_REMAIL
11363     if (cmailMailedMove) {
11364       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);
11365         DisplayError(msg, 0);
11366         return;
11367     }
11368 #endif
11369
11370     if (! (cmailMailedMove || RegisterMove())) return;
11371
11372     if (   cmailMailedMove
11373         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11374       snprintf(string, MSG_SIZ, partCommandString,
11375                appData.debugMode ? " -v" : "", appData.cmailGameName);
11376         commandOutput = popen(string, "r");
11377
11378         if (commandOutput == NULL) {
11379             DisplayError(_("Failed to invoke cmail"), 0);
11380         } else {
11381             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11382                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11383             }
11384             if (nBuffers > 1) {
11385                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11386                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11387                 nBytes = MSG_SIZ - 1;
11388             } else {
11389                 (void) memcpy(msg, buffer, nBytes);
11390             }
11391             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11392
11393             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11394                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11395
11396                 archived = TRUE;
11397                 for (i = 0; i < nCmailGames; i ++) {
11398                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11399                         archived = FALSE;
11400                     }
11401                 }
11402                 if (   archived
11403                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11404                         != NULL)) {
11405                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11406                            arcDir,
11407                            appData.cmailGameName,
11408                            gameInfo.date);
11409                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11410                     cmailMsgLoaded = FALSE;
11411                 }
11412             }
11413
11414             DisplayInformation(msg);
11415             pclose(commandOutput);
11416         }
11417     } else {
11418         if ((*cmailMsg) != '\0') {
11419             DisplayInformation(cmailMsg);
11420         }
11421     }
11422
11423     return;
11424 #endif /* !WIN32 */
11425 }
11426
11427 char *
11428 CmailMsg()
11429 {
11430 #if WIN32
11431     return NULL;
11432 #else
11433     int  prependComma = 0;
11434     char number[5];
11435     char string[MSG_SIZ];       /* Space for game-list */
11436     int  i;
11437
11438     if (!cmailMsgLoaded) return "";
11439
11440     if (cmailMailedMove) {
11441       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11442     } else {
11443         /* Create a list of games left */
11444       snprintf(string, MSG_SIZ, "[");
11445         for (i = 0; i < nCmailGames; i ++) {
11446             if (! (   cmailMoveRegistered[i]
11447                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11448                 if (prependComma) {
11449                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11450                 } else {
11451                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11452                     prependComma = 1;
11453                 }
11454
11455                 strcat(string, number);
11456             }
11457         }
11458         strcat(string, "]");
11459
11460         if (nCmailMovesRegistered + nCmailResults == 0) {
11461             switch (nCmailGames) {
11462               case 1:
11463                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11464                 break;
11465
11466               case 2:
11467                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11468                 break;
11469
11470               default:
11471                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11472                          nCmailGames);
11473                 break;
11474             }
11475         } else {
11476             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11477               case 1:
11478                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11479                          string);
11480                 break;
11481
11482               case 0:
11483                 if (nCmailResults == nCmailGames) {
11484                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11485                 } else {
11486                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11487                 }
11488                 break;
11489
11490               default:
11491                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11492                          string);
11493             }
11494         }
11495     }
11496     return cmailMsg;
11497 #endif /* WIN32 */
11498 }
11499
11500 void
11501 ResetGameEvent()
11502 {
11503     if (gameMode == Training)
11504       SetTrainingModeOff();
11505
11506     Reset(TRUE, TRUE);
11507     cmailMsgLoaded = FALSE;
11508     if (appData.icsActive) {
11509       SendToICS(ics_prefix);
11510       SendToICS("refresh\n");
11511     }
11512 }
11513
11514 void
11515 ExitEvent(status)
11516      int status;
11517 {
11518     exiting++;
11519     if (exiting > 2) {
11520       /* Give up on clean exit */
11521       exit(status);
11522     }
11523     if (exiting > 1) {
11524       /* Keep trying for clean exit */
11525       return;
11526     }
11527
11528     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11529
11530     if (telnetISR != NULL) {
11531       RemoveInputSource(telnetISR);
11532     }
11533     if (icsPR != NoProc) {
11534       DestroyChildProcess(icsPR, TRUE);
11535     }
11536
11537     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11538     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11539
11540     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11541     /* make sure this other one finishes before killing it!                  */
11542     if(endingGame) { int count = 0;
11543         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11544         while(endingGame && count++ < 10) DoSleep(1);
11545         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11546     }
11547
11548     /* Kill off chess programs */
11549     if (first.pr != NoProc) {
11550         ExitAnalyzeMode();
11551
11552         DoSleep( appData.delayBeforeQuit );
11553         SendToProgram("quit\n", &first);
11554         DoSleep( appData.delayAfterQuit );
11555         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11556     }
11557     if (second.pr != NoProc) {
11558         DoSleep( appData.delayBeforeQuit );
11559         SendToProgram("quit\n", &second);
11560         DoSleep( appData.delayAfterQuit );
11561         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11562     }
11563     if (first.isr != NULL) {
11564         RemoveInputSource(first.isr);
11565     }
11566     if (second.isr != NULL) {
11567         RemoveInputSource(second.isr);
11568     }
11569
11570     ShutDownFrontEnd();
11571     exit(status);
11572 }
11573
11574 void
11575 PauseEvent()
11576 {
11577     if (appData.debugMode)
11578         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11579     if (pausing) {
11580         pausing = FALSE;
11581         ModeHighlight();
11582         if (gameMode == MachinePlaysWhite ||
11583             gameMode == MachinePlaysBlack) {
11584             StartClocks();
11585         } else {
11586             DisplayBothClocks();
11587         }
11588         if (gameMode == PlayFromGameFile) {
11589             if (appData.timeDelay >= 0)
11590                 AutoPlayGameLoop();
11591         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11592             Reset(FALSE, TRUE);
11593             SendToICS(ics_prefix);
11594             SendToICS("refresh\n");
11595         } else if (currentMove < forwardMostMove) {
11596             ForwardInner(forwardMostMove);
11597         }
11598         pauseExamInvalid = FALSE;
11599     } else {
11600         switch (gameMode) {
11601           default:
11602             return;
11603           case IcsExamining:
11604             pauseExamForwardMostMove = forwardMostMove;
11605             pauseExamInvalid = FALSE;
11606             /* fall through */
11607           case IcsObserving:
11608           case IcsPlayingWhite:
11609           case IcsPlayingBlack:
11610             pausing = TRUE;
11611             ModeHighlight();
11612             return;
11613           case PlayFromGameFile:
11614             (void) StopLoadGameTimer();
11615             pausing = TRUE;
11616             ModeHighlight();
11617             break;
11618           case BeginningOfGame:
11619             if (appData.icsActive) return;
11620             /* else fall through */
11621           case MachinePlaysWhite:
11622           case MachinePlaysBlack:
11623           case TwoMachinesPlay:
11624             if (forwardMostMove == 0)
11625               return;           /* don't pause if no one has moved */
11626             if ((gameMode == MachinePlaysWhite &&
11627                  !WhiteOnMove(forwardMostMove)) ||
11628                 (gameMode == MachinePlaysBlack &&
11629                  WhiteOnMove(forwardMostMove))) {
11630                 StopClocks();
11631             }
11632             pausing = TRUE;
11633             ModeHighlight();
11634             break;
11635         }
11636     }
11637 }
11638
11639 void
11640 EditCommentEvent()
11641 {
11642     char title[MSG_SIZ];
11643
11644     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11645       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11646     } else {
11647       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11648                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11649                parseList[currentMove - 1]);
11650     }
11651
11652     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11653 }
11654
11655
11656 void
11657 EditTagsEvent()
11658 {
11659     char *tags = PGNTags(&gameInfo);
11660     EditTagsPopUp(tags, NULL);
11661     free(tags);
11662 }
11663
11664 void
11665 AnalyzeModeEvent()
11666 {
11667     if (appData.noChessProgram || gameMode == AnalyzeMode)
11668       return;
11669
11670     if (gameMode != AnalyzeFile) {
11671         if (!appData.icsEngineAnalyze) {
11672                EditGameEvent();
11673                if (gameMode != EditGame) return;
11674         }
11675         ResurrectChessProgram();
11676         SendToProgram("analyze\n", &first);
11677         first.analyzing = TRUE;
11678         /*first.maybeThinking = TRUE;*/
11679         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11680         EngineOutputPopUp();
11681     }
11682     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11683     pausing = FALSE;
11684     ModeHighlight();
11685     SetGameInfo();
11686
11687     StartAnalysisClock();
11688     GetTimeMark(&lastNodeCountTime);
11689     lastNodeCount = 0;
11690 }
11691
11692 void
11693 AnalyzeFileEvent()
11694 {
11695     if (appData.noChessProgram || gameMode == AnalyzeFile)
11696       return;
11697
11698     if (gameMode != AnalyzeMode) {
11699         EditGameEvent();
11700         if (gameMode != EditGame) return;
11701         ResurrectChessProgram();
11702         SendToProgram("analyze\n", &first);
11703         first.analyzing = TRUE;
11704         /*first.maybeThinking = TRUE;*/
11705         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11706         EngineOutputPopUp();
11707     }
11708     gameMode = AnalyzeFile;
11709     pausing = FALSE;
11710     ModeHighlight();
11711     SetGameInfo();
11712
11713     StartAnalysisClock();
11714     GetTimeMark(&lastNodeCountTime);
11715     lastNodeCount = 0;
11716 }
11717
11718 void
11719 MachineWhiteEvent()
11720 {
11721     char buf[MSG_SIZ];
11722     char *bookHit = NULL;
11723
11724     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11725       return;
11726
11727
11728     if (gameMode == PlayFromGameFile ||
11729         gameMode == TwoMachinesPlay  ||
11730         gameMode == Training         ||
11731         gameMode == AnalyzeMode      ||
11732         gameMode == EndOfGame)
11733         EditGameEvent();
11734
11735     if (gameMode == EditPosition)
11736         EditPositionDone(TRUE);
11737
11738     if (!WhiteOnMove(currentMove)) {
11739         DisplayError(_("It is not White's turn"), 0);
11740         return;
11741     }
11742
11743     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11744       ExitAnalyzeMode();
11745
11746     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11747         gameMode == AnalyzeFile)
11748         TruncateGame();
11749
11750     ResurrectChessProgram();    /* in case it isn't running */
11751     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11752         gameMode = MachinePlaysWhite;
11753         ResetClocks();
11754     } else
11755     gameMode = MachinePlaysWhite;
11756     pausing = FALSE;
11757     ModeHighlight();
11758     SetGameInfo();
11759     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11760     DisplayTitle(buf);
11761     if (first.sendName) {
11762       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11763       SendToProgram(buf, &first);
11764     }
11765     if (first.sendTime) {
11766       if (first.useColors) {
11767         SendToProgram("black\n", &first); /*gnu kludge*/
11768       }
11769       SendTimeRemaining(&first, TRUE);
11770     }
11771     if (first.useColors) {
11772       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11773     }
11774     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11775     SetMachineThinkingEnables();
11776     first.maybeThinking = TRUE;
11777     StartClocks();
11778     firstMove = FALSE;
11779
11780     if (appData.autoFlipView && !flipView) {
11781       flipView = !flipView;
11782       DrawPosition(FALSE, NULL);
11783       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11784     }
11785
11786     if(bookHit) { // [HGM] book: simulate book reply
11787         static char bookMove[MSG_SIZ]; // a bit generous?
11788
11789         programStats.nodes = programStats.depth = programStats.time =
11790         programStats.score = programStats.got_only_move = 0;
11791         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11792
11793         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11794         strcat(bookMove, bookHit);
11795         HandleMachineMove(bookMove, &first);
11796     }
11797 }
11798
11799 void
11800 MachineBlackEvent()
11801 {
11802   char buf[MSG_SIZ];
11803   char *bookHit = NULL;
11804
11805     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11806         return;
11807
11808
11809     if (gameMode == PlayFromGameFile ||
11810         gameMode == TwoMachinesPlay  ||
11811         gameMode == Training         ||
11812         gameMode == AnalyzeMode      ||
11813         gameMode == EndOfGame)
11814         EditGameEvent();
11815
11816     if (gameMode == EditPosition)
11817         EditPositionDone(TRUE);
11818
11819     if (WhiteOnMove(currentMove)) {
11820         DisplayError(_("It is not Black's turn"), 0);
11821         return;
11822     }
11823
11824     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11825       ExitAnalyzeMode();
11826
11827     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11828         gameMode == AnalyzeFile)
11829         TruncateGame();
11830
11831     ResurrectChessProgram();    /* in case it isn't running */
11832     gameMode = MachinePlaysBlack;
11833     pausing = FALSE;
11834     ModeHighlight();
11835     SetGameInfo();
11836     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11837     DisplayTitle(buf);
11838     if (first.sendName) {
11839       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11840       SendToProgram(buf, &first);
11841     }
11842     if (first.sendTime) {
11843       if (first.useColors) {
11844         SendToProgram("white\n", &first); /*gnu kludge*/
11845       }
11846       SendTimeRemaining(&first, FALSE);
11847     }
11848     if (first.useColors) {
11849       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11850     }
11851     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11852     SetMachineThinkingEnables();
11853     first.maybeThinking = TRUE;
11854     StartClocks();
11855
11856     if (appData.autoFlipView && flipView) {
11857       flipView = !flipView;
11858       DrawPosition(FALSE, NULL);
11859       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11860     }
11861     if(bookHit) { // [HGM] book: simulate book reply
11862         static char bookMove[MSG_SIZ]; // a bit generous?
11863
11864         programStats.nodes = programStats.depth = programStats.time =
11865         programStats.score = programStats.got_only_move = 0;
11866         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11867
11868         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11869         strcat(bookMove, bookHit);
11870         HandleMachineMove(bookMove, &first);
11871     }
11872 }
11873
11874
11875 void
11876 DisplayTwoMachinesTitle()
11877 {
11878     char buf[MSG_SIZ];
11879     if (appData.matchGames > 0) {
11880         if (first.twoMachinesColor[0] == 'w') {
11881           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11882                    gameInfo.white, gameInfo.black,
11883                    first.matchWins, second.matchWins,
11884                    matchGame - 1 - (first.matchWins + second.matchWins));
11885         } else {
11886           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11887                    gameInfo.white, gameInfo.black,
11888                    second.matchWins, first.matchWins,
11889                    matchGame - 1 - (first.matchWins + second.matchWins));
11890         }
11891     } else {
11892       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11893     }
11894     DisplayTitle(buf);
11895 }
11896
11897 void
11898 SettingsMenuIfReady()
11899 {
11900   if (second.lastPing != second.lastPong) {
11901     DisplayMessage("", _("Waiting for second chess program"));
11902     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11903     return;
11904   }
11905   ThawUI();
11906   DisplayMessage("", "");
11907   SettingsPopUp(&second);
11908 }
11909
11910 int
11911 WaitForSecond(DelayedEventCallback retry)
11912 {
11913     if (second.pr == NULL) {
11914         StartChessProgram(&second);
11915         if (second.protocolVersion == 1) {
11916           retry();
11917         } else {
11918           /* kludge: allow timeout for initial "feature" command */
11919           FreezeUI();
11920           DisplayMessage("", _("Starting second chess program"));
11921           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11922         }
11923         return 1;
11924     }
11925     return 0;
11926 }
11927
11928 void
11929 TwoMachinesEvent P((void))
11930 {
11931     int i;
11932     char buf[MSG_SIZ];
11933     ChessProgramState *onmove;
11934     char *bookHit = NULL;
11935
11936     if (appData.noChessProgram) return;
11937
11938     switch (gameMode) {
11939       case TwoMachinesPlay:
11940         return;
11941       case MachinePlaysWhite:
11942       case MachinePlaysBlack:
11943         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11944             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11945             return;
11946         }
11947         /* fall through */
11948       case BeginningOfGame:
11949       case PlayFromGameFile:
11950       case EndOfGame:
11951         EditGameEvent();
11952         if (gameMode != EditGame) return;
11953         break;
11954       case EditPosition:
11955         EditPositionDone(TRUE);
11956         break;
11957       case AnalyzeMode:
11958       case AnalyzeFile:
11959         ExitAnalyzeMode();
11960         break;
11961       case EditGame:
11962       default:
11963         break;
11964     }
11965
11966 //    forwardMostMove = currentMove;
11967     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11968     ResurrectChessProgram();    /* in case first program isn't running */
11969
11970     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11971     DisplayMessage("", "");
11972     InitChessProgram(&second, FALSE);
11973     SendToProgram("force\n", &second);
11974     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11975       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11976       return;
11977     }
11978     if (startedFromSetupPosition) {
11979         SendBoard(&second, backwardMostMove);
11980     if (appData.debugMode) {
11981         fprintf(debugFP, "Two Machines\n");
11982     }
11983     }
11984     for (i = backwardMostMove; i < forwardMostMove; i++) {
11985         SendMoveToProgram(i, &second);
11986     }
11987
11988     gameMode = TwoMachinesPlay;
11989     pausing = FALSE;
11990     ModeHighlight();
11991     SetGameInfo();
11992     DisplayTwoMachinesTitle();
11993     firstMove = TRUE;
11994     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11995         onmove = &first;
11996     } else {
11997         onmove = &second;
11998     }
11999
12000     SendToProgram(first.computerString, &first);
12001     if (first.sendName) {
12002       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12003       SendToProgram(buf, &first);
12004     }
12005     SendToProgram(second.computerString, &second);
12006     if (second.sendName) {
12007       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12008       SendToProgram(buf, &second);
12009     }
12010
12011     ResetClocks();
12012     if (!first.sendTime || !second.sendTime) {
12013         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12014         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12015     }
12016     if (onmove->sendTime) {
12017       if (onmove->useColors) {
12018         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12019       }
12020       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12021     }
12022     if (onmove->useColors) {
12023       SendToProgram(onmove->twoMachinesColor, onmove);
12024     }
12025     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12026 //    SendToProgram("go\n", onmove);
12027     onmove->maybeThinking = TRUE;
12028     SetMachineThinkingEnables();
12029
12030     StartClocks();
12031
12032     if(bookHit) { // [HGM] book: simulate book reply
12033         static char bookMove[MSG_SIZ]; // a bit generous?
12034
12035         programStats.nodes = programStats.depth = programStats.time =
12036         programStats.score = programStats.got_only_move = 0;
12037         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12038
12039         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12040         strcat(bookMove, bookHit);
12041         savedMessage = bookMove; // args for deferred call
12042         savedState = onmove;
12043         ScheduleDelayedEvent(DeferredBookMove, 1);
12044     }
12045 }
12046
12047 void
12048 TrainingEvent()
12049 {
12050     if (gameMode == Training) {
12051       SetTrainingModeOff();
12052       gameMode = PlayFromGameFile;
12053       DisplayMessage("", _("Training mode off"));
12054     } else {
12055       gameMode = Training;
12056       animateTraining = appData.animate;
12057
12058       /* make sure we are not already at the end of the game */
12059       if (currentMove < forwardMostMove) {
12060         SetTrainingModeOn();
12061         DisplayMessage("", _("Training mode on"));
12062       } else {
12063         gameMode = PlayFromGameFile;
12064         DisplayError(_("Already at end of game"), 0);
12065       }
12066     }
12067     ModeHighlight();
12068 }
12069
12070 void
12071 IcsClientEvent()
12072 {
12073     if (!appData.icsActive) return;
12074     switch (gameMode) {
12075       case IcsPlayingWhite:
12076       case IcsPlayingBlack:
12077       case IcsObserving:
12078       case IcsIdle:
12079       case BeginningOfGame:
12080       case IcsExamining:
12081         return;
12082
12083       case EditGame:
12084         break;
12085
12086       case EditPosition:
12087         EditPositionDone(TRUE);
12088         break;
12089
12090       case AnalyzeMode:
12091       case AnalyzeFile:
12092         ExitAnalyzeMode();
12093         break;
12094
12095       default:
12096         EditGameEvent();
12097         break;
12098     }
12099
12100     gameMode = IcsIdle;
12101     ModeHighlight();
12102     return;
12103 }
12104
12105
12106 void
12107 EditGameEvent()
12108 {
12109     int i;
12110
12111     switch (gameMode) {
12112       case Training:
12113         SetTrainingModeOff();
12114         break;
12115       case MachinePlaysWhite:
12116       case MachinePlaysBlack:
12117       case BeginningOfGame:
12118         SendToProgram("force\n", &first);
12119         SetUserThinkingEnables();
12120         break;
12121       case PlayFromGameFile:
12122         (void) StopLoadGameTimer();
12123         if (gameFileFP != NULL) {
12124             gameFileFP = NULL;
12125         }
12126         break;
12127       case EditPosition:
12128         EditPositionDone(TRUE);
12129         break;
12130       case AnalyzeMode:
12131       case AnalyzeFile:
12132         ExitAnalyzeMode();
12133         SendToProgram("force\n", &first);
12134         break;
12135       case TwoMachinesPlay:
12136         GameEnds(EndOfFile, NULL, GE_PLAYER);
12137         ResurrectChessProgram();
12138         SetUserThinkingEnables();
12139         break;
12140       case EndOfGame:
12141         ResurrectChessProgram();
12142         break;
12143       case IcsPlayingBlack:
12144       case IcsPlayingWhite:
12145         DisplayError(_("Warning: You are still playing a game"), 0);
12146         break;
12147       case IcsObserving:
12148         DisplayError(_("Warning: You are still observing a game"), 0);
12149         break;
12150       case IcsExamining:
12151         DisplayError(_("Warning: You are still examining a game"), 0);
12152         break;
12153       case IcsIdle:
12154         break;
12155       case EditGame:
12156       default:
12157         return;
12158     }
12159
12160     pausing = FALSE;
12161     StopClocks();
12162     first.offeredDraw = second.offeredDraw = 0;
12163
12164     if (gameMode == PlayFromGameFile) {
12165         whiteTimeRemaining = timeRemaining[0][currentMove];
12166         blackTimeRemaining = timeRemaining[1][currentMove];
12167         DisplayTitle("");
12168     }
12169
12170     if (gameMode == MachinePlaysWhite ||
12171         gameMode == MachinePlaysBlack ||
12172         gameMode == TwoMachinesPlay ||
12173         gameMode == EndOfGame) {
12174         i = forwardMostMove;
12175         while (i > currentMove) {
12176             SendToProgram("undo\n", &first);
12177             i--;
12178         }
12179         whiteTimeRemaining = timeRemaining[0][currentMove];
12180         blackTimeRemaining = timeRemaining[1][currentMove];
12181         DisplayBothClocks();
12182         if (whiteFlag || blackFlag) {
12183             whiteFlag = blackFlag = 0;
12184         }
12185         DisplayTitle("");
12186     }
12187
12188     gameMode = EditGame;
12189     ModeHighlight();
12190     SetGameInfo();
12191 }
12192
12193
12194 void
12195 EditPositionEvent()
12196 {
12197     if (gameMode == EditPosition) {
12198         EditGameEvent();
12199         return;
12200     }
12201
12202     EditGameEvent();
12203     if (gameMode != EditGame) return;
12204
12205     gameMode = EditPosition;
12206     ModeHighlight();
12207     SetGameInfo();
12208     if (currentMove > 0)
12209       CopyBoard(boards[0], boards[currentMove]);
12210
12211     blackPlaysFirst = !WhiteOnMove(currentMove);
12212     ResetClocks();
12213     currentMove = forwardMostMove = backwardMostMove = 0;
12214     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12215     DisplayMove(-1);
12216 }
12217
12218 void
12219 ExitAnalyzeMode()
12220 {
12221     /* [DM] icsEngineAnalyze - possible call from other functions */
12222     if (appData.icsEngineAnalyze) {
12223         appData.icsEngineAnalyze = FALSE;
12224
12225         DisplayMessage("",_("Close ICS engine analyze..."));
12226     }
12227     if (first.analysisSupport && first.analyzing) {
12228       SendToProgram("exit\n", &first);
12229       first.analyzing = FALSE;
12230     }
12231     thinkOutput[0] = NULLCHAR;
12232 }
12233
12234 void
12235 EditPositionDone(Boolean fakeRights)
12236 {
12237     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12238
12239     startedFromSetupPosition = TRUE;
12240     InitChessProgram(&first, FALSE);
12241     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12242       boards[0][EP_STATUS] = EP_NONE;
12243       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12244     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12245         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12246         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12247       } else boards[0][CASTLING][2] = NoRights;
12248     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12249         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12250         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12251       } else boards[0][CASTLING][5] = NoRights;
12252     }
12253     SendToProgram("force\n", &first);
12254     if (blackPlaysFirst) {
12255         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12256         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12257         currentMove = forwardMostMove = backwardMostMove = 1;
12258         CopyBoard(boards[1], boards[0]);
12259     } else {
12260         currentMove = forwardMostMove = backwardMostMove = 0;
12261     }
12262     SendBoard(&first, forwardMostMove);
12263     if (appData.debugMode) {
12264         fprintf(debugFP, "EditPosDone\n");
12265     }
12266     DisplayTitle("");
12267     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12268     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12269     gameMode = EditGame;
12270     ModeHighlight();
12271     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12272     ClearHighlights(); /* [AS] */
12273 }
12274
12275 /* Pause for `ms' milliseconds */
12276 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12277 void
12278 TimeDelay(ms)
12279      long ms;
12280 {
12281     TimeMark m1, m2;
12282
12283     GetTimeMark(&m1);
12284     do {
12285         GetTimeMark(&m2);
12286     } while (SubtractTimeMarks(&m2, &m1) < ms);
12287 }
12288
12289 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12290 void
12291 SendMultiLineToICS(buf)
12292      char *buf;
12293 {
12294     char temp[MSG_SIZ+1], *p;
12295     int len;
12296
12297     len = strlen(buf);
12298     if (len > MSG_SIZ)
12299       len = MSG_SIZ;
12300
12301     strncpy(temp, buf, len);
12302     temp[len] = 0;
12303
12304     p = temp;
12305     while (*p) {
12306         if (*p == '\n' || *p == '\r')
12307           *p = ' ';
12308         ++p;
12309     }
12310
12311     strcat(temp, "\n");
12312     SendToICS(temp);
12313     SendToPlayer(temp, strlen(temp));
12314 }
12315
12316 void
12317 SetWhiteToPlayEvent()
12318 {
12319     if (gameMode == EditPosition) {
12320         blackPlaysFirst = FALSE;
12321         DisplayBothClocks();    /* works because currentMove is 0 */
12322     } else if (gameMode == IcsExamining) {
12323         SendToICS(ics_prefix);
12324         SendToICS("tomove white\n");
12325     }
12326 }
12327
12328 void
12329 SetBlackToPlayEvent()
12330 {
12331     if (gameMode == EditPosition) {
12332         blackPlaysFirst = TRUE;
12333         currentMove = 1;        /* kludge */
12334         DisplayBothClocks();
12335         currentMove = 0;
12336     } else if (gameMode == IcsExamining) {
12337         SendToICS(ics_prefix);
12338         SendToICS("tomove black\n");
12339     }
12340 }
12341
12342 void
12343 EditPositionMenuEvent(selection, x, y)
12344      ChessSquare selection;
12345      int x, y;
12346 {
12347     char buf[MSG_SIZ];
12348     ChessSquare piece = boards[0][y][x];
12349
12350     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12351
12352     switch (selection) {
12353       case ClearBoard:
12354         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12355             SendToICS(ics_prefix);
12356             SendToICS("bsetup clear\n");
12357         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12358             SendToICS(ics_prefix);
12359             SendToICS("clearboard\n");
12360         } else {
12361             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12362                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12363                 for (y = 0; y < BOARD_HEIGHT; y++) {
12364                     if (gameMode == IcsExamining) {
12365                         if (boards[currentMove][y][x] != EmptySquare) {
12366                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12367                                     AAA + x, ONE + y);
12368                             SendToICS(buf);
12369                         }
12370                     } else {
12371                         boards[0][y][x] = p;
12372                     }
12373                 }
12374             }
12375         }
12376         if (gameMode == EditPosition) {
12377             DrawPosition(FALSE, boards[0]);
12378         }
12379         break;
12380
12381       case WhitePlay:
12382         SetWhiteToPlayEvent();
12383         break;
12384
12385       case BlackPlay:
12386         SetBlackToPlayEvent();
12387         break;
12388
12389       case EmptySquare:
12390         if (gameMode == IcsExamining) {
12391             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12392             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12393             SendToICS(buf);
12394         } else {
12395             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12396                 if(x == BOARD_LEFT-2) {
12397                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12398                     boards[0][y][1] = 0;
12399                 } else
12400                 if(x == BOARD_RGHT+1) {
12401                     if(y >= gameInfo.holdingsSize) break;
12402                     boards[0][y][BOARD_WIDTH-2] = 0;
12403                 } else break;
12404             }
12405             boards[0][y][x] = EmptySquare;
12406             DrawPosition(FALSE, boards[0]);
12407         }
12408         break;
12409
12410       case PromotePiece:
12411         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12412            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12413             selection = (ChessSquare) (PROMOTED piece);
12414         } else if(piece == EmptySquare) selection = WhiteSilver;
12415         else selection = (ChessSquare)((int)piece - 1);
12416         goto defaultlabel;
12417
12418       case DemotePiece:
12419         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12420            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12421             selection = (ChessSquare) (DEMOTED piece);
12422         } else if(piece == EmptySquare) selection = BlackSilver;
12423         else selection = (ChessSquare)((int)piece + 1);
12424         goto defaultlabel;
12425
12426       case WhiteQueen:
12427       case BlackQueen:
12428         if(gameInfo.variant == VariantShatranj ||
12429            gameInfo.variant == VariantXiangqi  ||
12430            gameInfo.variant == VariantCourier  ||
12431            gameInfo.variant == VariantMakruk     )
12432             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12433         goto defaultlabel;
12434
12435       case WhiteKing:
12436       case BlackKing:
12437         if(gameInfo.variant == VariantXiangqi)
12438             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12439         if(gameInfo.variant == VariantKnightmate)
12440             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12441       default:
12442         defaultlabel:
12443         if (gameMode == IcsExamining) {
12444             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12445             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12446                      PieceToChar(selection), AAA + x, ONE + y);
12447             SendToICS(buf);
12448         } else {
12449             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12450                 int n;
12451                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12452                     n = PieceToNumber(selection - BlackPawn);
12453                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12454                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12455                     boards[0][BOARD_HEIGHT-1-n][1]++;
12456                 } else
12457                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12458                     n = PieceToNumber(selection);
12459                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12460                     boards[0][n][BOARD_WIDTH-1] = selection;
12461                     boards[0][n][BOARD_WIDTH-2]++;
12462                 }
12463             } else
12464             boards[0][y][x] = selection;
12465             DrawPosition(TRUE, boards[0]);
12466         }
12467         break;
12468     }
12469 }
12470
12471
12472 void
12473 DropMenuEvent(selection, x, y)
12474      ChessSquare selection;
12475      int x, y;
12476 {
12477     ChessMove moveType;
12478
12479     switch (gameMode) {
12480       case IcsPlayingWhite:
12481       case MachinePlaysBlack:
12482         if (!WhiteOnMove(currentMove)) {
12483             DisplayMoveError(_("It is Black's turn"));
12484             return;
12485         }
12486         moveType = WhiteDrop;
12487         break;
12488       case IcsPlayingBlack:
12489       case MachinePlaysWhite:
12490         if (WhiteOnMove(currentMove)) {
12491             DisplayMoveError(_("It is White's turn"));
12492             return;
12493         }
12494         moveType = BlackDrop;
12495         break;
12496       case EditGame:
12497         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12498         break;
12499       default:
12500         return;
12501     }
12502
12503     if (moveType == BlackDrop && selection < BlackPawn) {
12504       selection = (ChessSquare) ((int) selection
12505                                  + (int) BlackPawn - (int) WhitePawn);
12506     }
12507     if (boards[currentMove][y][x] != EmptySquare) {
12508         DisplayMoveError(_("That square is occupied"));
12509         return;
12510     }
12511
12512     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12513 }
12514
12515 void
12516 AcceptEvent()
12517 {
12518     /* Accept a pending offer of any kind from opponent */
12519
12520     if (appData.icsActive) {
12521         SendToICS(ics_prefix);
12522         SendToICS("accept\n");
12523     } else if (cmailMsgLoaded) {
12524         if (currentMove == cmailOldMove &&
12525             commentList[cmailOldMove] != NULL &&
12526             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12527                    "Black offers a draw" : "White offers a draw")) {
12528             TruncateGame();
12529             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12530             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12531         } else {
12532             DisplayError(_("There is no pending offer on this move"), 0);
12533             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12534         }
12535     } else {
12536         /* Not used for offers from chess program */
12537     }
12538 }
12539
12540 void
12541 DeclineEvent()
12542 {
12543     /* Decline a pending offer of any kind from opponent */
12544
12545     if (appData.icsActive) {
12546         SendToICS(ics_prefix);
12547         SendToICS("decline\n");
12548     } else if (cmailMsgLoaded) {
12549         if (currentMove == cmailOldMove &&
12550             commentList[cmailOldMove] != NULL &&
12551             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12552                    "Black offers a draw" : "White offers a draw")) {
12553 #ifdef NOTDEF
12554             AppendComment(cmailOldMove, "Draw declined", TRUE);
12555             DisplayComment(cmailOldMove - 1, "Draw declined");
12556 #endif /*NOTDEF*/
12557         } else {
12558             DisplayError(_("There is no pending offer on this move"), 0);
12559         }
12560     } else {
12561         /* Not used for offers from chess program */
12562     }
12563 }
12564
12565 void
12566 RematchEvent()
12567 {
12568     /* Issue ICS rematch command */
12569     if (appData.icsActive) {
12570         SendToICS(ics_prefix);
12571         SendToICS("rematch\n");
12572     }
12573 }
12574
12575 void
12576 CallFlagEvent()
12577 {
12578     /* Call your opponent's flag (claim a win on time) */
12579     if (appData.icsActive) {
12580         SendToICS(ics_prefix);
12581         SendToICS("flag\n");
12582     } else {
12583         switch (gameMode) {
12584           default:
12585             return;
12586           case MachinePlaysWhite:
12587             if (whiteFlag) {
12588                 if (blackFlag)
12589                   GameEnds(GameIsDrawn, "Both players ran out of time",
12590                            GE_PLAYER);
12591                 else
12592                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12593             } else {
12594                 DisplayError(_("Your opponent is not out of time"), 0);
12595             }
12596             break;
12597           case MachinePlaysBlack:
12598             if (blackFlag) {
12599                 if (whiteFlag)
12600                   GameEnds(GameIsDrawn, "Both players ran out of time",
12601                            GE_PLAYER);
12602                 else
12603                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12604             } else {
12605                 DisplayError(_("Your opponent is not out of time"), 0);
12606             }
12607             break;
12608         }
12609     }
12610 }
12611
12612 void
12613 ClockClick(int which)
12614 {       // [HGM] code moved to back-end from winboard.c
12615         if(which) { // black clock
12616           if (gameMode == EditPosition || gameMode == IcsExamining) {
12617             SetBlackToPlayEvent();
12618           } else if (gameMode == EditGame || shiftKey) {
12619             AdjustClock(which, -1);
12620           } else if (gameMode == IcsPlayingWhite ||
12621                      gameMode == MachinePlaysBlack) {
12622             CallFlagEvent();
12623           }
12624         } else { // white clock
12625           if (gameMode == EditPosition || gameMode == IcsExamining) {
12626             SetWhiteToPlayEvent();
12627           } else if (gameMode == EditGame || shiftKey) {
12628             AdjustClock(which, -1);
12629           } else if (gameMode == IcsPlayingBlack ||
12630                    gameMode == MachinePlaysWhite) {
12631             CallFlagEvent();
12632           }
12633         }
12634 }
12635
12636 void
12637 DrawEvent()
12638 {
12639     /* Offer draw or accept pending draw offer from opponent */
12640
12641     if (appData.icsActive) {
12642         /* Note: tournament rules require draw offers to be
12643            made after you make your move but before you punch
12644            your clock.  Currently ICS doesn't let you do that;
12645            instead, you immediately punch your clock after making
12646            a move, but you can offer a draw at any time. */
12647
12648         SendToICS(ics_prefix);
12649         SendToICS("draw\n");
12650         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12651     } else if (cmailMsgLoaded) {
12652         if (currentMove == cmailOldMove &&
12653             commentList[cmailOldMove] != NULL &&
12654             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12655                    "Black offers a draw" : "White offers a draw")) {
12656             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12657             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12658         } else if (currentMove == cmailOldMove + 1) {
12659             char *offer = WhiteOnMove(cmailOldMove) ?
12660               "White offers a draw" : "Black offers a draw";
12661             AppendComment(currentMove, offer, TRUE);
12662             DisplayComment(currentMove - 1, offer);
12663             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12664         } else {
12665             DisplayError(_("You must make your move before offering a draw"), 0);
12666             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12667         }
12668     } else if (first.offeredDraw) {
12669         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12670     } else {
12671         if (first.sendDrawOffers) {
12672             SendToProgram("draw\n", &first);
12673             userOfferedDraw = TRUE;
12674         }
12675     }
12676 }
12677
12678 void
12679 AdjournEvent()
12680 {
12681     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12682
12683     if (appData.icsActive) {
12684         SendToICS(ics_prefix);
12685         SendToICS("adjourn\n");
12686     } else {
12687         /* Currently GNU Chess doesn't offer or accept Adjourns */
12688     }
12689 }
12690
12691
12692 void
12693 AbortEvent()
12694 {
12695     /* Offer Abort or accept pending Abort offer from opponent */
12696
12697     if (appData.icsActive) {
12698         SendToICS(ics_prefix);
12699         SendToICS("abort\n");
12700     } else {
12701         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12702     }
12703 }
12704
12705 void
12706 ResignEvent()
12707 {
12708     /* Resign.  You can do this even if it's not your turn. */
12709
12710     if (appData.icsActive) {
12711         SendToICS(ics_prefix);
12712         SendToICS("resign\n");
12713     } else {
12714         switch (gameMode) {
12715           case MachinePlaysWhite:
12716             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12717             break;
12718           case MachinePlaysBlack:
12719             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12720             break;
12721           case EditGame:
12722             if (cmailMsgLoaded) {
12723                 TruncateGame();
12724                 if (WhiteOnMove(cmailOldMove)) {
12725                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12726                 } else {
12727                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12728                 }
12729                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12730             }
12731             break;
12732           default:
12733             break;
12734         }
12735     }
12736 }
12737
12738
12739 void
12740 StopObservingEvent()
12741 {
12742     /* Stop observing current games */
12743     SendToICS(ics_prefix);
12744     SendToICS("unobserve\n");
12745 }
12746
12747 void
12748 StopExaminingEvent()
12749 {
12750     /* Stop observing current game */
12751     SendToICS(ics_prefix);
12752     SendToICS("unexamine\n");
12753 }
12754
12755 void
12756 ForwardInner(target)
12757      int target;
12758 {
12759     int limit;
12760
12761     if (appData.debugMode)
12762         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12763                 target, currentMove, forwardMostMove);
12764
12765     if (gameMode == EditPosition)
12766       return;
12767
12768     if (gameMode == PlayFromGameFile && !pausing)
12769       PauseEvent();
12770
12771     if (gameMode == IcsExamining && pausing)
12772       limit = pauseExamForwardMostMove;
12773     else
12774       limit = forwardMostMove;
12775
12776     if (target > limit) target = limit;
12777
12778     if (target > 0 && moveList[target - 1][0]) {
12779         int fromX, fromY, toX, toY;
12780         toX = moveList[target - 1][2] - AAA;
12781         toY = moveList[target - 1][3] - ONE;
12782         if (moveList[target - 1][1] == '@') {
12783             if (appData.highlightLastMove) {
12784                 SetHighlights(-1, -1, toX, toY);
12785             }
12786         } else {
12787             fromX = moveList[target - 1][0] - AAA;
12788             fromY = moveList[target - 1][1] - ONE;
12789             if (target == currentMove + 1) {
12790                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12791             }
12792             if (appData.highlightLastMove) {
12793                 SetHighlights(fromX, fromY, toX, toY);
12794             }
12795         }
12796     }
12797     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12798         gameMode == Training || gameMode == PlayFromGameFile ||
12799         gameMode == AnalyzeFile) {
12800         while (currentMove < target) {
12801             SendMoveToProgram(currentMove++, &first);
12802         }
12803     } else {
12804         currentMove = target;
12805     }
12806
12807     if (gameMode == EditGame || gameMode == EndOfGame) {
12808         whiteTimeRemaining = timeRemaining[0][currentMove];
12809         blackTimeRemaining = timeRemaining[1][currentMove];
12810     }
12811     DisplayBothClocks();
12812     DisplayMove(currentMove - 1);
12813     DrawPosition(FALSE, boards[currentMove]);
12814     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12815     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12816         DisplayComment(currentMove - 1, commentList[currentMove]);
12817     }
12818 }
12819
12820
12821 void
12822 ForwardEvent()
12823 {
12824     if (gameMode == IcsExamining && !pausing) {
12825         SendToICS(ics_prefix);
12826         SendToICS("forward\n");
12827     } else {
12828         ForwardInner(currentMove + 1);
12829     }
12830 }
12831
12832 void
12833 ToEndEvent()
12834 {
12835     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12836         /* to optimze, we temporarily turn off analysis mode while we feed
12837          * the remaining moves to the engine. Otherwise we get analysis output
12838          * after each move.
12839          */
12840         if (first.analysisSupport) {
12841           SendToProgram("exit\nforce\n", &first);
12842           first.analyzing = FALSE;
12843         }
12844     }
12845
12846     if (gameMode == IcsExamining && !pausing) {
12847         SendToICS(ics_prefix);
12848         SendToICS("forward 999999\n");
12849     } else {
12850         ForwardInner(forwardMostMove);
12851     }
12852
12853     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12854         /* we have fed all the moves, so reactivate analysis mode */
12855         SendToProgram("analyze\n", &first);
12856         first.analyzing = TRUE;
12857         /*first.maybeThinking = TRUE;*/
12858         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12859     }
12860 }
12861
12862 void
12863 BackwardInner(target)
12864      int target;
12865 {
12866     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12867
12868     if (appData.debugMode)
12869         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12870                 target, currentMove, forwardMostMove);
12871
12872     if (gameMode == EditPosition) return;
12873     if (currentMove <= backwardMostMove) {
12874         ClearHighlights();
12875         DrawPosition(full_redraw, boards[currentMove]);
12876         return;
12877     }
12878     if (gameMode == PlayFromGameFile && !pausing)
12879       PauseEvent();
12880
12881     if (moveList[target][0]) {
12882         int fromX, fromY, toX, toY;
12883         toX = moveList[target][2] - AAA;
12884         toY = moveList[target][3] - ONE;
12885         if (moveList[target][1] == '@') {
12886             if (appData.highlightLastMove) {
12887                 SetHighlights(-1, -1, toX, toY);
12888             }
12889         } else {
12890             fromX = moveList[target][0] - AAA;
12891             fromY = moveList[target][1] - ONE;
12892             if (target == currentMove - 1) {
12893                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12894             }
12895             if (appData.highlightLastMove) {
12896                 SetHighlights(fromX, fromY, toX, toY);
12897             }
12898         }
12899     }
12900     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12901         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12902         while (currentMove > target) {
12903             SendToProgram("undo\n", &first);
12904             currentMove--;
12905         }
12906     } else {
12907         currentMove = target;
12908     }
12909
12910     if (gameMode == EditGame || gameMode == EndOfGame) {
12911         whiteTimeRemaining = timeRemaining[0][currentMove];
12912         blackTimeRemaining = timeRemaining[1][currentMove];
12913     }
12914     DisplayBothClocks();
12915     DisplayMove(currentMove - 1);
12916     DrawPosition(full_redraw, boards[currentMove]);
12917     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12918     // [HGM] PV info: routine tests if comment empty
12919     DisplayComment(currentMove - 1, commentList[currentMove]);
12920 }
12921
12922 void
12923 BackwardEvent()
12924 {
12925     if (gameMode == IcsExamining && !pausing) {
12926         SendToICS(ics_prefix);
12927         SendToICS("backward\n");
12928     } else {
12929         BackwardInner(currentMove - 1);
12930     }
12931 }
12932
12933 void
12934 ToStartEvent()
12935 {
12936     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12937         /* to optimize, we temporarily turn off analysis mode while we undo
12938          * all the moves. Otherwise we get analysis output after each undo.
12939          */
12940         if (first.analysisSupport) {
12941           SendToProgram("exit\nforce\n", &first);
12942           first.analyzing = FALSE;
12943         }
12944     }
12945
12946     if (gameMode == IcsExamining && !pausing) {
12947         SendToICS(ics_prefix);
12948         SendToICS("backward 999999\n");
12949     } else {
12950         BackwardInner(backwardMostMove);
12951     }
12952
12953     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12954         /* we have fed all the moves, so reactivate analysis mode */
12955         SendToProgram("analyze\n", &first);
12956         first.analyzing = TRUE;
12957         /*first.maybeThinking = TRUE;*/
12958         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12959     }
12960 }
12961
12962 void
12963 ToNrEvent(int to)
12964 {
12965   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12966   if (to >= forwardMostMove) to = forwardMostMove;
12967   if (to <= backwardMostMove) to = backwardMostMove;
12968   if (to < currentMove) {
12969     BackwardInner(to);
12970   } else {
12971     ForwardInner(to);
12972   }
12973 }
12974
12975 void
12976 RevertEvent(Boolean annotate)
12977 {
12978     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12979         return;
12980     }
12981     if (gameMode != IcsExamining) {
12982         DisplayError(_("You are not examining a game"), 0);
12983         return;
12984     }
12985     if (pausing) {
12986         DisplayError(_("You can't revert while pausing"), 0);
12987         return;
12988     }
12989     SendToICS(ics_prefix);
12990     SendToICS("revert\n");
12991 }
12992
12993 void
12994 RetractMoveEvent()
12995 {
12996     switch (gameMode) {
12997       case MachinePlaysWhite:
12998       case MachinePlaysBlack:
12999         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13000             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13001             return;
13002         }
13003         if (forwardMostMove < 2) return;
13004         currentMove = forwardMostMove = forwardMostMove - 2;
13005         whiteTimeRemaining = timeRemaining[0][currentMove];
13006         blackTimeRemaining = timeRemaining[1][currentMove];
13007         DisplayBothClocks();
13008         DisplayMove(currentMove - 1);
13009         ClearHighlights();/*!! could figure this out*/
13010         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13011         SendToProgram("remove\n", &first);
13012         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13013         break;
13014
13015       case BeginningOfGame:
13016       default:
13017         break;
13018
13019       case IcsPlayingWhite:
13020       case IcsPlayingBlack:
13021         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13022             SendToICS(ics_prefix);
13023             SendToICS("takeback 2\n");
13024         } else {
13025             SendToICS(ics_prefix);
13026             SendToICS("takeback 1\n");
13027         }
13028         break;
13029     }
13030 }
13031
13032 void
13033 MoveNowEvent()
13034 {
13035     ChessProgramState *cps;
13036
13037     switch (gameMode) {
13038       case MachinePlaysWhite:
13039         if (!WhiteOnMove(forwardMostMove)) {
13040             DisplayError(_("It is your turn"), 0);
13041             return;
13042         }
13043         cps = &first;
13044         break;
13045       case MachinePlaysBlack:
13046         if (WhiteOnMove(forwardMostMove)) {
13047             DisplayError(_("It is your turn"), 0);
13048             return;
13049         }
13050         cps = &first;
13051         break;
13052       case TwoMachinesPlay:
13053         if (WhiteOnMove(forwardMostMove) ==
13054             (first.twoMachinesColor[0] == 'w')) {
13055             cps = &first;
13056         } else {
13057             cps = &second;
13058         }
13059         break;
13060       case BeginningOfGame:
13061       default:
13062         return;
13063     }
13064     SendToProgram("?\n", cps);
13065 }
13066
13067 void
13068 TruncateGameEvent()
13069 {
13070     EditGameEvent();
13071     if (gameMode != EditGame) return;
13072     TruncateGame();
13073 }
13074
13075 void
13076 TruncateGame()
13077 {
13078     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13079     if (forwardMostMove > currentMove) {
13080         if (gameInfo.resultDetails != NULL) {
13081             free(gameInfo.resultDetails);
13082             gameInfo.resultDetails = NULL;
13083             gameInfo.result = GameUnfinished;
13084         }
13085         forwardMostMove = currentMove;
13086         HistorySet(parseList, backwardMostMove, forwardMostMove,
13087                    currentMove-1);
13088     }
13089 }
13090
13091 void
13092 HintEvent()
13093 {
13094     if (appData.noChessProgram) return;
13095     switch (gameMode) {
13096       case MachinePlaysWhite:
13097         if (WhiteOnMove(forwardMostMove)) {
13098             DisplayError(_("Wait until your turn"), 0);
13099             return;
13100         }
13101         break;
13102       case BeginningOfGame:
13103       case MachinePlaysBlack:
13104         if (!WhiteOnMove(forwardMostMove)) {
13105             DisplayError(_("Wait until your turn"), 0);
13106             return;
13107         }
13108         break;
13109       default:
13110         DisplayError(_("No hint available"), 0);
13111         return;
13112     }
13113     SendToProgram("hint\n", &first);
13114     hintRequested = TRUE;
13115 }
13116
13117 void
13118 BookEvent()
13119 {
13120     if (appData.noChessProgram) return;
13121     switch (gameMode) {
13122       case MachinePlaysWhite:
13123         if (WhiteOnMove(forwardMostMove)) {
13124             DisplayError(_("Wait until your turn"), 0);
13125             return;
13126         }
13127         break;
13128       case BeginningOfGame:
13129       case MachinePlaysBlack:
13130         if (!WhiteOnMove(forwardMostMove)) {
13131             DisplayError(_("Wait until your turn"), 0);
13132             return;
13133         }
13134         break;
13135       case EditPosition:
13136         EditPositionDone(TRUE);
13137         break;
13138       case TwoMachinesPlay:
13139         return;
13140       default:
13141         break;
13142     }
13143     SendToProgram("bk\n", &first);
13144     bookOutput[0] = NULLCHAR;
13145     bookRequested = TRUE;
13146 }
13147
13148 void
13149 AboutGameEvent()
13150 {
13151     char *tags = PGNTags(&gameInfo);
13152     TagsPopUp(tags, CmailMsg());
13153     free(tags);
13154 }
13155
13156 /* end button procedures */
13157
13158 void
13159 PrintPosition(fp, move)
13160      FILE *fp;
13161      int move;
13162 {
13163     int i, j;
13164
13165     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13166         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13167             char c = PieceToChar(boards[move][i][j]);
13168             fputc(c == 'x' ? '.' : c, fp);
13169             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13170         }
13171     }
13172     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13173       fprintf(fp, "white to play\n");
13174     else
13175       fprintf(fp, "black to play\n");
13176 }
13177
13178 void
13179 PrintOpponents(fp)
13180      FILE *fp;
13181 {
13182     if (gameInfo.white != NULL) {
13183         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13184     } else {
13185         fprintf(fp, "\n");
13186     }
13187 }
13188
13189 /* Find last component of program's own name, using some heuristics */
13190 void
13191 TidyProgramName(prog, host, buf)
13192      char *prog, *host, buf[MSG_SIZ];
13193 {
13194     char *p, *q;
13195     int local = (strcmp(host, "localhost") == 0);
13196     while (!local && (p = strchr(prog, ';')) != NULL) {
13197         p++;
13198         while (*p == ' ') p++;
13199         prog = p;
13200     }
13201     if (*prog == '"' || *prog == '\'') {
13202         q = strchr(prog + 1, *prog);
13203     } else {
13204         q = strchr(prog, ' ');
13205     }
13206     if (q == NULL) q = prog + strlen(prog);
13207     p = q;
13208     while (p >= prog && *p != '/' && *p != '\\') p--;
13209     p++;
13210     if(p == prog && *p == '"') p++;
13211     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13212     memcpy(buf, p, q - p);
13213     buf[q - p] = NULLCHAR;
13214     if (!local) {
13215         strcat(buf, "@");
13216         strcat(buf, host);
13217     }
13218 }
13219
13220 char *
13221 TimeControlTagValue()
13222 {
13223     char buf[MSG_SIZ];
13224     if (!appData.clockMode) {
13225       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13226     } else if (movesPerSession > 0) {
13227       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13228     } else if (timeIncrement == 0) {
13229       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13230     } else {
13231       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13232     }
13233     return StrSave(buf);
13234 }
13235
13236 void
13237 SetGameInfo()
13238 {
13239     /* This routine is used only for certain modes */
13240     VariantClass v = gameInfo.variant;
13241     ChessMove r = GameUnfinished;
13242     char *p = NULL;
13243
13244     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13245         r = gameInfo.result;
13246         p = gameInfo.resultDetails;
13247         gameInfo.resultDetails = NULL;
13248     }
13249     ClearGameInfo(&gameInfo);
13250     gameInfo.variant = v;
13251
13252     switch (gameMode) {
13253       case MachinePlaysWhite:
13254         gameInfo.event = StrSave( appData.pgnEventHeader );
13255         gameInfo.site = StrSave(HostName());
13256         gameInfo.date = PGNDate();
13257         gameInfo.round = StrSave("-");
13258         gameInfo.white = StrSave(first.tidy);
13259         gameInfo.black = StrSave(UserName());
13260         gameInfo.timeControl = TimeControlTagValue();
13261         break;
13262
13263       case MachinePlaysBlack:
13264         gameInfo.event = StrSave( appData.pgnEventHeader );
13265         gameInfo.site = StrSave(HostName());
13266         gameInfo.date = PGNDate();
13267         gameInfo.round = StrSave("-");
13268         gameInfo.white = StrSave(UserName());
13269         gameInfo.black = StrSave(first.tidy);
13270         gameInfo.timeControl = TimeControlTagValue();
13271         break;
13272
13273       case TwoMachinesPlay:
13274         gameInfo.event = StrSave( appData.pgnEventHeader );
13275         gameInfo.site = StrSave(HostName());
13276         gameInfo.date = PGNDate();
13277         if (matchGame > 0) {
13278             char buf[MSG_SIZ];
13279             snprintf(buf, MSG_SIZ, "%d", matchGame);
13280             gameInfo.round = StrSave(buf);
13281         } else {
13282             gameInfo.round = StrSave("-");
13283         }
13284         if (first.twoMachinesColor[0] == 'w') {
13285             gameInfo.white = StrSave(first.tidy);
13286             gameInfo.black = StrSave(second.tidy);
13287         } else {
13288             gameInfo.white = StrSave(second.tidy);
13289             gameInfo.black = StrSave(first.tidy);
13290         }
13291         gameInfo.timeControl = TimeControlTagValue();
13292         break;
13293
13294       case EditGame:
13295         gameInfo.event = StrSave("Edited game");
13296         gameInfo.site = StrSave(HostName());
13297         gameInfo.date = PGNDate();
13298         gameInfo.round = StrSave("-");
13299         gameInfo.white = StrSave("-");
13300         gameInfo.black = StrSave("-");
13301         gameInfo.result = r;
13302         gameInfo.resultDetails = p;
13303         break;
13304
13305       case EditPosition:
13306         gameInfo.event = StrSave("Edited position");
13307         gameInfo.site = StrSave(HostName());
13308         gameInfo.date = PGNDate();
13309         gameInfo.round = StrSave("-");
13310         gameInfo.white = StrSave("-");
13311         gameInfo.black = StrSave("-");
13312         break;
13313
13314       case IcsPlayingWhite:
13315       case IcsPlayingBlack:
13316       case IcsObserving:
13317       case IcsExamining:
13318         break;
13319
13320       case PlayFromGameFile:
13321         gameInfo.event = StrSave("Game from non-PGN file");
13322         gameInfo.site = StrSave(HostName());
13323         gameInfo.date = PGNDate();
13324         gameInfo.round = StrSave("-");
13325         gameInfo.white = StrSave("?");
13326         gameInfo.black = StrSave("?");
13327         break;
13328
13329       default:
13330         break;
13331     }
13332 }
13333
13334 void
13335 ReplaceComment(index, text)
13336      int index;
13337      char *text;
13338 {
13339     int len;
13340     char *p;
13341     float score;
13342
13343     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13344        pvInfoList[index-1].depth == len &&
13345        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13346        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13347     while (*text == '\n') text++;
13348     len = strlen(text);
13349     while (len > 0 && text[len - 1] == '\n') len--;
13350
13351     if (commentList[index] != NULL)
13352       free(commentList[index]);
13353
13354     if (len == 0) {
13355         commentList[index] = NULL;
13356         return;
13357     }
13358   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13359       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13360       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13361     commentList[index] = (char *) malloc(len + 2);
13362     strncpy(commentList[index], text, len);
13363     commentList[index][len] = '\n';
13364     commentList[index][len + 1] = NULLCHAR;
13365   } else {
13366     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13367     char *p;
13368     commentList[index] = (char *) malloc(len + 7);
13369     safeStrCpy(commentList[index], "{\n", 3);
13370     safeStrCpy(commentList[index]+2, text, len+1);
13371     commentList[index][len+2] = NULLCHAR;
13372     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13373     strcat(commentList[index], "\n}\n");
13374   }
13375 }
13376
13377 void
13378 CrushCRs(text)
13379      char *text;
13380 {
13381   char *p = text;
13382   char *q = text;
13383   char ch;
13384
13385   do {
13386     ch = *p++;
13387     if (ch == '\r') continue;
13388     *q++ = ch;
13389   } while (ch != '\0');
13390 }
13391
13392 void
13393 AppendComment(index, text, addBraces)
13394      int index;
13395      char *text;
13396      Boolean addBraces; // [HGM] braces: tells if we should add {}
13397 {
13398     int oldlen, len;
13399     char *old;
13400
13401 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13402     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13403
13404     CrushCRs(text);
13405     while (*text == '\n') text++;
13406     len = strlen(text);
13407     while (len > 0 && text[len - 1] == '\n') len--;
13408
13409     if (len == 0) return;
13410
13411     if (commentList[index] != NULL) {
13412         old = commentList[index];
13413         oldlen = strlen(old);
13414         while(commentList[index][oldlen-1] ==  '\n')
13415           commentList[index][--oldlen] = NULLCHAR;
13416         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13417         safeStrCpy(commentList[index], old, oldlen + len + 6);
13418         free(old);
13419         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13420         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13421           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13422           while (*text == '\n') { text++; len--; }
13423           commentList[index][--oldlen] = NULLCHAR;
13424       }
13425         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13426         else          strcat(commentList[index], "\n");
13427         strcat(commentList[index], text);
13428         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13429         else          strcat(commentList[index], "\n");
13430     } else {
13431         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13432         if(addBraces)
13433           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13434         else commentList[index][0] = NULLCHAR;
13435         strcat(commentList[index], text);
13436         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13437         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13438     }
13439 }
13440
13441 static char * FindStr( char * text, char * sub_text )
13442 {
13443     char * result = strstr( text, sub_text );
13444
13445     if( result != NULL ) {
13446         result += strlen( sub_text );
13447     }
13448
13449     return result;
13450 }
13451
13452 /* [AS] Try to extract PV info from PGN comment */
13453 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13454 char *GetInfoFromComment( int index, char * text )
13455 {
13456     char * sep = text, *p;
13457
13458     if( text != NULL && index > 0 ) {
13459         int score = 0;
13460         int depth = 0;
13461         int time = -1, sec = 0, deci;
13462         char * s_eval = FindStr( text, "[%eval " );
13463         char * s_emt = FindStr( text, "[%emt " );
13464
13465         if( s_eval != NULL || s_emt != NULL ) {
13466             /* New style */
13467             char delim;
13468
13469             if( s_eval != NULL ) {
13470                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13471                     return text;
13472                 }
13473
13474                 if( delim != ']' ) {
13475                     return text;
13476                 }
13477             }
13478
13479             if( s_emt != NULL ) {
13480             }
13481                 return text;
13482         }
13483         else {
13484             /* We expect something like: [+|-]nnn.nn/dd */
13485             int score_lo = 0;
13486
13487             if(*text != '{') return text; // [HGM] braces: must be normal comment
13488
13489             sep = strchr( text, '/' );
13490             if( sep == NULL || sep < (text+4) ) {
13491                 return text;
13492             }
13493
13494             p = text;
13495             if(p[1] == '(') { // comment starts with PV
13496                p = strchr(p, ')'); // locate end of PV
13497                if(p == NULL || sep < p+5) return text;
13498                // at this point we have something like "{(.*) +0.23/6 ..."
13499                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13500                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13501                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13502             }
13503             time = -1; sec = -1; deci = -1;
13504             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13505                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13506                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13507                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13508                 return text;
13509             }
13510
13511             if( score_lo < 0 || score_lo >= 100 ) {
13512                 return text;
13513             }
13514
13515             if(sec >= 0) time = 600*time + 10*sec; else
13516             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13517
13518             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13519
13520             /* [HGM] PV time: now locate end of PV info */
13521             while( *++sep >= '0' && *sep <= '9'); // strip depth
13522             if(time >= 0)
13523             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13524             if(sec >= 0)
13525             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13526             if(deci >= 0)
13527             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13528             while(*sep == ' ') sep++;
13529         }
13530
13531         if( depth <= 0 ) {
13532             return text;
13533         }
13534
13535         if( time < 0 ) {
13536             time = -1;
13537         }
13538
13539         pvInfoList[index-1].depth = depth;
13540         pvInfoList[index-1].score = score;
13541         pvInfoList[index-1].time  = 10*time; // centi-sec
13542         if(*sep == '}') *sep = 0; else *--sep = '{';
13543         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13544     }
13545     return sep;
13546 }
13547
13548 void
13549 SendToProgram(message, cps)
13550      char *message;
13551      ChessProgramState *cps;
13552 {
13553     int count, outCount, error;
13554     char buf[MSG_SIZ];
13555
13556     if (cps->pr == NULL) return;
13557     Attention(cps);
13558
13559     if (appData.debugMode) {
13560         TimeMark now;
13561         GetTimeMark(&now);
13562         fprintf(debugFP, "%ld >%-6s: %s",
13563                 SubtractTimeMarks(&now, &programStartTime),
13564                 cps->which, message);
13565     }
13566
13567     count = strlen(message);
13568     outCount = OutputToProcess(cps->pr, message, count, &error);
13569     if (outCount < count && !exiting
13570                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13571       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13572         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13573             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13574                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13575                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13576             } else {
13577                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13578             }
13579             gameInfo.resultDetails = StrSave(buf);
13580         }
13581         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13582     }
13583 }
13584
13585 void
13586 ReceiveFromProgram(isr, closure, message, count, error)
13587      InputSourceRef isr;
13588      VOIDSTAR closure;
13589      char *message;
13590      int count;
13591      int error;
13592 {
13593     char *end_str;
13594     char buf[MSG_SIZ];
13595     ChessProgramState *cps = (ChessProgramState *)closure;
13596
13597     if (isr != cps->isr) return; /* Killed intentionally */
13598     if (count <= 0) {
13599         if (count == 0) {
13600             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13601                     _(cps->which), cps->program);
13602         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13603                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13604                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13605                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13606                 } else {
13607                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13608                 }
13609                 gameInfo.resultDetails = StrSave(buf);
13610             }
13611             RemoveInputSource(cps->isr);
13612             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13613         } else {
13614             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13615                     _(cps->which), cps->program);
13616             RemoveInputSource(cps->isr);
13617
13618             /* [AS] Program is misbehaving badly... kill it */
13619             if( count == -2 ) {
13620                 DestroyChildProcess( cps->pr, 9 );
13621                 cps->pr = NoProc;
13622             }
13623
13624             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13625         }
13626         return;
13627     }
13628
13629     if ((end_str = strchr(message, '\r')) != NULL)
13630       *end_str = NULLCHAR;
13631     if ((end_str = strchr(message, '\n')) != NULL)
13632       *end_str = NULLCHAR;
13633
13634     if (appData.debugMode) {
13635         TimeMark now; int print = 1;
13636         char *quote = ""; char c; int i;
13637
13638         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13639                 char start = message[0];
13640                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13641                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13642                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13643                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13644                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13645                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13646                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13647                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13648                    sscanf(message, "hint: %c", &c)!=1 && 
13649                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13650                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13651                     print = (appData.engineComments >= 2);
13652                 }
13653                 message[0] = start; // restore original message
13654         }
13655         if(print) {
13656                 GetTimeMark(&now);
13657                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13658                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13659                         quote,
13660                         message);
13661         }
13662     }
13663
13664     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13665     if (appData.icsEngineAnalyze) {
13666         if (strstr(message, "whisper") != NULL ||
13667              strstr(message, "kibitz") != NULL ||
13668             strstr(message, "tellics") != NULL) return;
13669     }
13670
13671     HandleMachineMove(message, cps);
13672 }
13673
13674
13675 void
13676 SendTimeControl(cps, mps, tc, inc, sd, st)
13677      ChessProgramState *cps;
13678      int mps, inc, sd, st;
13679      long tc;
13680 {
13681     char buf[MSG_SIZ];
13682     int seconds;
13683
13684     if( timeControl_2 > 0 ) {
13685         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13686             tc = timeControl_2;
13687         }
13688     }
13689     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13690     inc /= cps->timeOdds;
13691     st  /= cps->timeOdds;
13692
13693     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13694
13695     if (st > 0) {
13696       /* Set exact time per move, normally using st command */
13697       if (cps->stKludge) {
13698         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13699         seconds = st % 60;
13700         if (seconds == 0) {
13701           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13702         } else {
13703           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13704         }
13705       } else {
13706         snprintf(buf, MSG_SIZ, "st %d\n", st);
13707       }
13708     } else {
13709       /* Set conventional or incremental time control, using level command */
13710       if (seconds == 0) {
13711         /* Note old gnuchess bug -- minutes:seconds used to not work.
13712            Fixed in later versions, but still avoid :seconds
13713            when seconds is 0. */
13714         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13715       } else {
13716         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13717                  seconds, inc/1000.);
13718       }
13719     }
13720     SendToProgram(buf, cps);
13721
13722     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13723     /* Orthogonally, limit search to given depth */
13724     if (sd > 0) {
13725       if (cps->sdKludge) {
13726         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13727       } else {
13728         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13729       }
13730       SendToProgram(buf, cps);
13731     }
13732
13733     if(cps->nps >= 0) { /* [HGM] nps */
13734         if(cps->supportsNPS == FALSE)
13735           cps->nps = -1; // don't use if engine explicitly says not supported!
13736         else {
13737           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13738           SendToProgram(buf, cps);
13739         }
13740     }
13741 }
13742
13743 ChessProgramState *WhitePlayer()
13744 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13745 {
13746     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13747        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13748         return &second;
13749     return &first;
13750 }
13751
13752 void
13753 SendTimeRemaining(cps, machineWhite)
13754      ChessProgramState *cps;
13755      int /*boolean*/ machineWhite;
13756 {
13757     char message[MSG_SIZ];
13758     long time, otime;
13759
13760     /* Note: this routine must be called when the clocks are stopped
13761        or when they have *just* been set or switched; otherwise
13762        it will be off by the time since the current tick started.
13763     */
13764     if (machineWhite) {
13765         time = whiteTimeRemaining / 10;
13766         otime = blackTimeRemaining / 10;
13767     } else {
13768         time = blackTimeRemaining / 10;
13769         otime = whiteTimeRemaining / 10;
13770     }
13771     /* [HGM] translate opponent's time by time-odds factor */
13772     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13773     if (appData.debugMode) {
13774         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13775     }
13776
13777     if (time <= 0) time = 1;
13778     if (otime <= 0) otime = 1;
13779
13780     snprintf(message, MSG_SIZ, "time %ld\n", time);
13781     SendToProgram(message, cps);
13782
13783     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13784     SendToProgram(message, cps);
13785 }
13786
13787 int
13788 BoolFeature(p, name, loc, cps)
13789      char **p;
13790      char *name;
13791      int *loc;
13792      ChessProgramState *cps;
13793 {
13794   char buf[MSG_SIZ];
13795   int len = strlen(name);
13796   int val;
13797
13798   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13799     (*p) += len + 1;
13800     sscanf(*p, "%d", &val);
13801     *loc = (val != 0);
13802     while (**p && **p != ' ')
13803       (*p)++;
13804     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13805     SendToProgram(buf, cps);
13806     return TRUE;
13807   }
13808   return FALSE;
13809 }
13810
13811 int
13812 IntFeature(p, name, loc, cps)
13813      char **p;
13814      char *name;
13815      int *loc;
13816      ChessProgramState *cps;
13817 {
13818   char buf[MSG_SIZ];
13819   int len = strlen(name);
13820   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13821     (*p) += len + 1;
13822     sscanf(*p, "%d", loc);
13823     while (**p && **p != ' ') (*p)++;
13824     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13825     SendToProgram(buf, cps);
13826     return TRUE;
13827   }
13828   return FALSE;
13829 }
13830
13831 int
13832 StringFeature(p, name, loc, cps)
13833      char **p;
13834      char *name;
13835      char loc[];
13836      ChessProgramState *cps;
13837 {
13838   char buf[MSG_SIZ];
13839   int len = strlen(name);
13840   if (strncmp((*p), name, len) == 0
13841       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13842     (*p) += len + 2;
13843     sscanf(*p, "%[^\"]", loc);
13844     while (**p && **p != '\"') (*p)++;
13845     if (**p == '\"') (*p)++;
13846     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13847     SendToProgram(buf, cps);
13848     return TRUE;
13849   }
13850   return FALSE;
13851 }
13852
13853 int
13854 ParseOption(Option *opt, ChessProgramState *cps)
13855 // [HGM] options: process the string that defines an engine option, and determine
13856 // name, type, default value, and allowed value range
13857 {
13858         char *p, *q, buf[MSG_SIZ];
13859         int n, min = (-1)<<31, max = 1<<31, def;
13860
13861         if(p = strstr(opt->name, " -spin ")) {
13862             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13863             if(max < min) max = min; // enforce consistency
13864             if(def < min) def = min;
13865             if(def > max) def = max;
13866             opt->value = def;
13867             opt->min = min;
13868             opt->max = max;
13869             opt->type = Spin;
13870         } else if((p = strstr(opt->name, " -slider "))) {
13871             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13872             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13873             if(max < min) max = min; // enforce consistency
13874             if(def < min) def = min;
13875             if(def > max) def = max;
13876             opt->value = def;
13877             opt->min = min;
13878             opt->max = max;
13879             opt->type = Spin; // Slider;
13880         } else if((p = strstr(opt->name, " -string "))) {
13881             opt->textValue = p+9;
13882             opt->type = TextBox;
13883         } else if((p = strstr(opt->name, " -file "))) {
13884             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13885             opt->textValue = p+7;
13886             opt->type = FileName; // FileName;
13887         } else if((p = strstr(opt->name, " -path "))) {
13888             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13889             opt->textValue = p+7;
13890             opt->type = PathName; // PathName;
13891         } else if(p = strstr(opt->name, " -check ")) {
13892             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13893             opt->value = (def != 0);
13894             opt->type = CheckBox;
13895         } else if(p = strstr(opt->name, " -combo ")) {
13896             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13897             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13898             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13899             opt->value = n = 0;
13900             while(q = StrStr(q, " /// ")) {
13901                 n++; *q = 0;    // count choices, and null-terminate each of them
13902                 q += 5;
13903                 if(*q == '*') { // remember default, which is marked with * prefix
13904                     q++;
13905                     opt->value = n;
13906                 }
13907                 cps->comboList[cps->comboCnt++] = q;
13908             }
13909             cps->comboList[cps->comboCnt++] = NULL;
13910             opt->max = n + 1;
13911             opt->type = ComboBox;
13912         } else if(p = strstr(opt->name, " -button")) {
13913             opt->type = Button;
13914         } else if(p = strstr(opt->name, " -save")) {
13915             opt->type = SaveButton;
13916         } else return FALSE;
13917         *p = 0; // terminate option name
13918         // now look if the command-line options define a setting for this engine option.
13919         if(cps->optionSettings && cps->optionSettings[0])
13920             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13921         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13922           snprintf(buf, MSG_SIZ, "option %s", p);
13923                 if(p = strstr(buf, ",")) *p = 0;
13924                 if(q = strchr(buf, '=')) switch(opt->type) {
13925                     case ComboBox:
13926                         for(n=0; n<opt->max; n++)
13927                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13928                         break;
13929                     case TextBox:
13930                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13931                         break;
13932                     case Spin:
13933                     case CheckBox:
13934                         opt->value = atoi(q+1);
13935                     default:
13936                         break;
13937                 }
13938                 strcat(buf, "\n");
13939                 SendToProgram(buf, cps);
13940         }
13941         return TRUE;
13942 }
13943
13944 void
13945 FeatureDone(cps, val)
13946      ChessProgramState* cps;
13947      int val;
13948 {
13949   DelayedEventCallback cb = GetDelayedEvent();
13950   if ((cb == InitBackEnd3 && cps == &first) ||
13951       (cb == SettingsMenuIfReady && cps == &second) ||
13952       (cb == TwoMachinesEventIfReady && cps == &second)) {
13953     CancelDelayedEvent();
13954     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13955   }
13956   cps->initDone = val;
13957 }
13958
13959 /* Parse feature command from engine */
13960 void
13961 ParseFeatures(args, cps)
13962      char* args;
13963      ChessProgramState *cps;
13964 {
13965   char *p = args;
13966   char *q;
13967   int val;
13968   char buf[MSG_SIZ];
13969
13970   for (;;) {
13971     while (*p == ' ') p++;
13972     if (*p == NULLCHAR) return;
13973
13974     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13975     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13976     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13977     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13978     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13979     if (BoolFeature(&p, "reuse", &val, cps)) {
13980       /* Engine can disable reuse, but can't enable it if user said no */
13981       if (!val) cps->reuse = FALSE;
13982       continue;
13983     }
13984     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13985     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13986       if (gameMode == TwoMachinesPlay) {
13987         DisplayTwoMachinesTitle();
13988       } else {
13989         DisplayTitle("");
13990       }
13991       continue;
13992     }
13993     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13994     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13995     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13996     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13997     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13998     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13999     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14000     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14001     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14002     if (IntFeature(&p, "done", &val, cps)) {
14003       FeatureDone(cps, val);
14004       continue;
14005     }
14006     /* Added by Tord: */
14007     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14008     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14009     /* End of additions by Tord */
14010
14011     /* [HGM] added features: */
14012     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14013     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14014     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14015     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14016     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14017     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14018     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14019         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14020           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14021             SendToProgram(buf, cps);
14022             continue;
14023         }
14024         if(cps->nrOptions >= MAX_OPTIONS) {
14025             cps->nrOptions--;
14026             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14027             DisplayError(buf, 0);
14028         }
14029         continue;
14030     }
14031     /* End of additions by HGM */
14032
14033     /* unknown feature: complain and skip */
14034     q = p;
14035     while (*q && *q != '=') q++;
14036     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14037     SendToProgram(buf, cps);
14038     p = q;
14039     if (*p == '=') {
14040       p++;
14041       if (*p == '\"') {
14042         p++;
14043         while (*p && *p != '\"') p++;
14044         if (*p == '\"') p++;
14045       } else {
14046         while (*p && *p != ' ') p++;
14047       }
14048     }
14049   }
14050
14051 }
14052
14053 void
14054 PeriodicUpdatesEvent(newState)
14055      int newState;
14056 {
14057     if (newState == appData.periodicUpdates)
14058       return;
14059
14060     appData.periodicUpdates=newState;
14061
14062     /* Display type changes, so update it now */
14063 //    DisplayAnalysis();
14064
14065     /* Get the ball rolling again... */
14066     if (newState) {
14067         AnalysisPeriodicEvent(1);
14068         StartAnalysisClock();
14069     }
14070 }
14071
14072 void
14073 PonderNextMoveEvent(newState)
14074      int newState;
14075 {
14076     if (newState == appData.ponderNextMove) return;
14077     if (gameMode == EditPosition) EditPositionDone(TRUE);
14078     if (newState) {
14079         SendToProgram("hard\n", &first);
14080         if (gameMode == TwoMachinesPlay) {
14081             SendToProgram("hard\n", &second);
14082         }
14083     } else {
14084         SendToProgram("easy\n", &first);
14085         thinkOutput[0] = NULLCHAR;
14086         if (gameMode == TwoMachinesPlay) {
14087             SendToProgram("easy\n", &second);
14088         }
14089     }
14090     appData.ponderNextMove = newState;
14091 }
14092
14093 void
14094 NewSettingEvent(option, feature, command, value)
14095      char *command;
14096      int option, value, *feature;
14097 {
14098     char buf[MSG_SIZ];
14099
14100     if (gameMode == EditPosition) EditPositionDone(TRUE);
14101     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14102     if(feature == NULL || *feature) SendToProgram(buf, &first);
14103     if (gameMode == TwoMachinesPlay) {
14104         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14105     }
14106 }
14107
14108 void
14109 ShowThinkingEvent()
14110 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14111 {
14112     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14113     int newState = appData.showThinking
14114         // [HGM] thinking: other features now need thinking output as well
14115         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14116
14117     if (oldState == newState) return;
14118     oldState = newState;
14119     if (gameMode == EditPosition) EditPositionDone(TRUE);
14120     if (oldState) {
14121         SendToProgram("post\n", &first);
14122         if (gameMode == TwoMachinesPlay) {
14123             SendToProgram("post\n", &second);
14124         }
14125     } else {
14126         SendToProgram("nopost\n", &first);
14127         thinkOutput[0] = NULLCHAR;
14128         if (gameMode == TwoMachinesPlay) {
14129             SendToProgram("nopost\n", &second);
14130         }
14131     }
14132 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14133 }
14134
14135 void
14136 AskQuestionEvent(title, question, replyPrefix, which)
14137      char *title; char *question; char *replyPrefix; char *which;
14138 {
14139   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14140   if (pr == NoProc) return;
14141   AskQuestion(title, question, replyPrefix, pr);
14142 }
14143
14144 void
14145 DisplayMove(moveNumber)
14146      int moveNumber;
14147 {
14148     char message[MSG_SIZ];
14149     char res[MSG_SIZ];
14150     char cpThinkOutput[MSG_SIZ];
14151
14152     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14153
14154     if (moveNumber == forwardMostMove - 1 ||
14155         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14156
14157         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14158
14159         if (strchr(cpThinkOutput, '\n')) {
14160             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14161         }
14162     } else {
14163         *cpThinkOutput = NULLCHAR;
14164     }
14165
14166     /* [AS] Hide thinking from human user */
14167     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14168         *cpThinkOutput = NULLCHAR;
14169         if( thinkOutput[0] != NULLCHAR ) {
14170             int i;
14171
14172             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14173                 cpThinkOutput[i] = '.';
14174             }
14175             cpThinkOutput[i] = NULLCHAR;
14176             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14177         }
14178     }
14179
14180     if (moveNumber == forwardMostMove - 1 &&
14181         gameInfo.resultDetails != NULL) {
14182         if (gameInfo.resultDetails[0] == NULLCHAR) {
14183           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14184         } else {
14185           snprintf(res, MSG_SIZ, " {%s} %s",
14186                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14187         }
14188     } else {
14189         res[0] = NULLCHAR;
14190     }
14191
14192     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14193         DisplayMessage(res, cpThinkOutput);
14194     } else {
14195       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14196                 WhiteOnMove(moveNumber) ? " " : ".. ",
14197                 parseList[moveNumber], res);
14198         DisplayMessage(message, cpThinkOutput);
14199     }
14200 }
14201
14202 void
14203 DisplayComment(moveNumber, text)
14204      int moveNumber;
14205      char *text;
14206 {
14207     char title[MSG_SIZ];
14208     char buf[8000]; // comment can be long!
14209     int score, depth;
14210
14211     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14212       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14213     } else {
14214       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14215               WhiteOnMove(moveNumber) ? " " : ".. ",
14216               parseList[moveNumber]);
14217     }
14218     // [HGM] PV info: display PV info together with (or as) comment
14219     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14220       if(text == NULL) text = "";
14221       score = pvInfoList[moveNumber].score;
14222       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14223               depth, (pvInfoList[moveNumber].time+50)/100, text);
14224       text = buf;
14225     }
14226     if (text != NULL && (appData.autoDisplayComment || commentUp))
14227         CommentPopUp(title, text);
14228 }
14229
14230 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14231  * might be busy thinking or pondering.  It can be omitted if your
14232  * gnuchess is configured to stop thinking immediately on any user
14233  * input.  However, that gnuchess feature depends on the FIONREAD
14234  * ioctl, which does not work properly on some flavors of Unix.
14235  */
14236 void
14237 Attention(cps)
14238      ChessProgramState *cps;
14239 {
14240 #if ATTENTION
14241     if (!cps->useSigint) return;
14242     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14243     switch (gameMode) {
14244       case MachinePlaysWhite:
14245       case MachinePlaysBlack:
14246       case TwoMachinesPlay:
14247       case IcsPlayingWhite:
14248       case IcsPlayingBlack:
14249       case AnalyzeMode:
14250       case AnalyzeFile:
14251         /* Skip if we know it isn't thinking */
14252         if (!cps->maybeThinking) return;
14253         if (appData.debugMode)
14254           fprintf(debugFP, "Interrupting %s\n", cps->which);
14255         InterruptChildProcess(cps->pr);
14256         cps->maybeThinking = FALSE;
14257         break;
14258       default:
14259         break;
14260     }
14261 #endif /*ATTENTION*/
14262 }
14263
14264 int
14265 CheckFlags()
14266 {
14267     if (whiteTimeRemaining <= 0) {
14268         if (!whiteFlag) {
14269             whiteFlag = TRUE;
14270             if (appData.icsActive) {
14271                 if (appData.autoCallFlag &&
14272                     gameMode == IcsPlayingBlack && !blackFlag) {
14273                   SendToICS(ics_prefix);
14274                   SendToICS("flag\n");
14275                 }
14276             } else {
14277                 if (blackFlag) {
14278                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14279                 } else {
14280                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14281                     if (appData.autoCallFlag) {
14282                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14283                         return TRUE;
14284                     }
14285                 }
14286             }
14287         }
14288     }
14289     if (blackTimeRemaining <= 0) {
14290         if (!blackFlag) {
14291             blackFlag = TRUE;
14292             if (appData.icsActive) {
14293                 if (appData.autoCallFlag &&
14294                     gameMode == IcsPlayingWhite && !whiteFlag) {
14295                   SendToICS(ics_prefix);
14296                   SendToICS("flag\n");
14297                 }
14298             } else {
14299                 if (whiteFlag) {
14300                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14301                 } else {
14302                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14303                     if (appData.autoCallFlag) {
14304                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14305                         return TRUE;
14306                     }
14307                 }
14308             }
14309         }
14310     }
14311     return FALSE;
14312 }
14313
14314 void
14315 CheckTimeControl()
14316 {
14317     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14318         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14319
14320     /*
14321      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14322      */
14323     if ( !WhiteOnMove(forwardMostMove) ) {
14324         /* White made time control */
14325         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14326         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14327         /* [HGM] time odds: correct new time quota for time odds! */
14328                                             / WhitePlayer()->timeOdds;
14329         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14330     } else {
14331         lastBlack -= blackTimeRemaining;
14332         /* Black made time control */
14333         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14334                                             / WhitePlayer()->other->timeOdds;
14335         lastWhite = whiteTimeRemaining;
14336     }
14337 }
14338
14339 void
14340 DisplayBothClocks()
14341 {
14342     int wom = gameMode == EditPosition ?
14343       !blackPlaysFirst : WhiteOnMove(currentMove);
14344     DisplayWhiteClock(whiteTimeRemaining, wom);
14345     DisplayBlackClock(blackTimeRemaining, !wom);
14346 }
14347
14348
14349 /* Timekeeping seems to be a portability nightmare.  I think everyone
14350    has ftime(), but I'm really not sure, so I'm including some ifdefs
14351    to use other calls if you don't.  Clocks will be less accurate if
14352    you have neither ftime nor gettimeofday.
14353 */
14354
14355 /* VS 2008 requires the #include outside of the function */
14356 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14357 #include <sys/timeb.h>
14358 #endif
14359
14360 /* Get the current time as a TimeMark */
14361 void
14362 GetTimeMark(tm)
14363      TimeMark *tm;
14364 {
14365 #if HAVE_GETTIMEOFDAY
14366
14367     struct timeval timeVal;
14368     struct timezone timeZone;
14369
14370     gettimeofday(&timeVal, &timeZone);
14371     tm->sec = (long) timeVal.tv_sec;
14372     tm->ms = (int) (timeVal.tv_usec / 1000L);
14373
14374 #else /*!HAVE_GETTIMEOFDAY*/
14375 #if HAVE_FTIME
14376
14377 // include <sys/timeb.h> / moved to just above start of function
14378     struct timeb timeB;
14379
14380     ftime(&timeB);
14381     tm->sec = (long) timeB.time;
14382     tm->ms = (int) timeB.millitm;
14383
14384 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14385     tm->sec = (long) time(NULL);
14386     tm->ms = 0;
14387 #endif
14388 #endif
14389 }
14390
14391 /* Return the difference in milliseconds between two
14392    time marks.  We assume the difference will fit in a long!
14393 */
14394 long
14395 SubtractTimeMarks(tm2, tm1)
14396      TimeMark *tm2, *tm1;
14397 {
14398     return 1000L*(tm2->sec - tm1->sec) +
14399            (long) (tm2->ms - tm1->ms);
14400 }
14401
14402
14403 /*
14404  * Code to manage the game clocks.
14405  *
14406  * In tournament play, black starts the clock and then white makes a move.
14407  * We give the human user a slight advantage if he is playing white---the
14408  * clocks don't run until he makes his first move, so it takes zero time.
14409  * Also, we don't account for network lag, so we could get out of sync
14410  * with GNU Chess's clock -- but then, referees are always right.
14411  */
14412
14413 static TimeMark tickStartTM;
14414 static long intendedTickLength;
14415
14416 long
14417 NextTickLength(timeRemaining)
14418      long timeRemaining;
14419 {
14420     long nominalTickLength, nextTickLength;
14421
14422     if (timeRemaining > 0L && timeRemaining <= 10000L)
14423       nominalTickLength = 100L;
14424     else
14425       nominalTickLength = 1000L;
14426     nextTickLength = timeRemaining % nominalTickLength;
14427     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14428
14429     return nextTickLength;
14430 }
14431
14432 /* Adjust clock one minute up or down */
14433 void
14434 AdjustClock(Boolean which, int dir)
14435 {
14436     if(which) blackTimeRemaining += 60000*dir;
14437     else      whiteTimeRemaining += 60000*dir;
14438     DisplayBothClocks();
14439 }
14440
14441 /* Stop clocks and reset to a fresh time control */
14442 void
14443 ResetClocks()
14444 {
14445     (void) StopClockTimer();
14446     if (appData.icsActive) {
14447         whiteTimeRemaining = blackTimeRemaining = 0;
14448     } else if (searchTime) {
14449         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14450         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14451     } else { /* [HGM] correct new time quote for time odds */
14452         whiteTC = blackTC = fullTimeControlString;
14453         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14454         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14455     }
14456     if (whiteFlag || blackFlag) {
14457         DisplayTitle("");
14458         whiteFlag = blackFlag = FALSE;
14459     }
14460     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14461     DisplayBothClocks();
14462 }
14463
14464 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14465
14466 /* Decrement running clock by amount of time that has passed */
14467 void
14468 DecrementClocks()
14469 {
14470     long timeRemaining;
14471     long lastTickLength, fudge;
14472     TimeMark now;
14473
14474     if (!appData.clockMode) return;
14475     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14476
14477     GetTimeMark(&now);
14478
14479     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14480
14481     /* Fudge if we woke up a little too soon */
14482     fudge = intendedTickLength - lastTickLength;
14483     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14484
14485     if (WhiteOnMove(forwardMostMove)) {
14486         if(whiteNPS >= 0) lastTickLength = 0;
14487         timeRemaining = whiteTimeRemaining -= lastTickLength;
14488         if(timeRemaining < 0 && !appData.icsActive) {
14489             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14490             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14491                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14492                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14493             }
14494         }
14495         DisplayWhiteClock(whiteTimeRemaining - fudge,
14496                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14497     } else {
14498         if(blackNPS >= 0) lastTickLength = 0;
14499         timeRemaining = blackTimeRemaining -= lastTickLength;
14500         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14501             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14502             if(suddenDeath) {
14503                 blackStartMove = forwardMostMove;
14504                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14505             }
14506         }
14507         DisplayBlackClock(blackTimeRemaining - fudge,
14508                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14509     }
14510     if (CheckFlags()) return;
14511
14512     tickStartTM = now;
14513     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14514     StartClockTimer(intendedTickLength);
14515
14516     /* if the time remaining has fallen below the alarm threshold, sound the
14517      * alarm. if the alarm has sounded and (due to a takeback or time control
14518      * with increment) the time remaining has increased to a level above the
14519      * threshold, reset the alarm so it can sound again.
14520      */
14521
14522     if (appData.icsActive && appData.icsAlarm) {
14523
14524         /* make sure we are dealing with the user's clock */
14525         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14526                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14527            )) return;
14528
14529         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14530             alarmSounded = FALSE;
14531         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14532             PlayAlarmSound();
14533             alarmSounded = TRUE;
14534         }
14535     }
14536 }
14537
14538
14539 /* A player has just moved, so stop the previously running
14540    clock and (if in clock mode) start the other one.
14541    We redisplay both clocks in case we're in ICS mode, because
14542    ICS gives us an update to both clocks after every move.
14543    Note that this routine is called *after* forwardMostMove
14544    is updated, so the last fractional tick must be subtracted
14545    from the color that is *not* on move now.
14546 */
14547 void
14548 SwitchClocks(int newMoveNr)
14549 {
14550     long lastTickLength;
14551     TimeMark now;
14552     int flagged = FALSE;
14553
14554     GetTimeMark(&now);
14555
14556     if (StopClockTimer() && appData.clockMode) {
14557         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14558         if (!WhiteOnMove(forwardMostMove)) {
14559             if(blackNPS >= 0) lastTickLength = 0;
14560             blackTimeRemaining -= lastTickLength;
14561            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14562 //         if(pvInfoList[forwardMostMove].time == -1)
14563                  pvInfoList[forwardMostMove].time =               // use GUI time
14564                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14565         } else {
14566            if(whiteNPS >= 0) lastTickLength = 0;
14567            whiteTimeRemaining -= lastTickLength;
14568            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14569 //         if(pvInfoList[forwardMostMove].time == -1)
14570                  pvInfoList[forwardMostMove].time =
14571                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14572         }
14573         flagged = CheckFlags();
14574     }
14575     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14576     CheckTimeControl();
14577
14578     if (flagged || !appData.clockMode) return;
14579
14580     switch (gameMode) {
14581       case MachinePlaysBlack:
14582       case MachinePlaysWhite:
14583       case BeginningOfGame:
14584         if (pausing) return;
14585         break;
14586
14587       case EditGame:
14588       case PlayFromGameFile:
14589       case IcsExamining:
14590         return;
14591
14592       default:
14593         break;
14594     }
14595
14596     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14597         if(WhiteOnMove(forwardMostMove))
14598              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14599         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14600     }
14601
14602     tickStartTM = now;
14603     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14604       whiteTimeRemaining : blackTimeRemaining);
14605     StartClockTimer(intendedTickLength);
14606 }
14607
14608
14609 /* Stop both clocks */
14610 void
14611 StopClocks()
14612 {
14613     long lastTickLength;
14614     TimeMark now;
14615
14616     if (!StopClockTimer()) return;
14617     if (!appData.clockMode) return;
14618
14619     GetTimeMark(&now);
14620
14621     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14622     if (WhiteOnMove(forwardMostMove)) {
14623         if(whiteNPS >= 0) lastTickLength = 0;
14624         whiteTimeRemaining -= lastTickLength;
14625         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14626     } else {
14627         if(blackNPS >= 0) lastTickLength = 0;
14628         blackTimeRemaining -= lastTickLength;
14629         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14630     }
14631     CheckFlags();
14632 }
14633
14634 /* Start clock of player on move.  Time may have been reset, so
14635    if clock is already running, stop and restart it. */
14636 void
14637 StartClocks()
14638 {
14639     (void) StopClockTimer(); /* in case it was running already */
14640     DisplayBothClocks();
14641     if (CheckFlags()) return;
14642
14643     if (!appData.clockMode) return;
14644     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14645
14646     GetTimeMark(&tickStartTM);
14647     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14648       whiteTimeRemaining : blackTimeRemaining);
14649
14650    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14651     whiteNPS = blackNPS = -1;
14652     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14653        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14654         whiteNPS = first.nps;
14655     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14656        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14657         blackNPS = first.nps;
14658     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14659         whiteNPS = second.nps;
14660     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14661         blackNPS = second.nps;
14662     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14663
14664     StartClockTimer(intendedTickLength);
14665 }
14666
14667 char *
14668 TimeString(ms)
14669      long ms;
14670 {
14671     long second, minute, hour, day;
14672     char *sign = "";
14673     static char buf[32];
14674
14675     if (ms > 0 && ms <= 9900) {
14676       /* convert milliseconds to tenths, rounding up */
14677       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14678
14679       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14680       return buf;
14681     }
14682
14683     /* convert milliseconds to seconds, rounding up */
14684     /* use floating point to avoid strangeness of integer division
14685        with negative dividends on many machines */
14686     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14687
14688     if (second < 0) {
14689         sign = "-";
14690         second = -second;
14691     }
14692
14693     day = second / (60 * 60 * 24);
14694     second = second % (60 * 60 * 24);
14695     hour = second / (60 * 60);
14696     second = second % (60 * 60);
14697     minute = second / 60;
14698     second = second % 60;
14699
14700     if (day > 0)
14701       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14702               sign, day, hour, minute, second);
14703     else if (hour > 0)
14704       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14705     else
14706       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14707
14708     return buf;
14709 }
14710
14711
14712 /*
14713  * This is necessary because some C libraries aren't ANSI C compliant yet.
14714  */
14715 char *
14716 StrStr(string, match)
14717      char *string, *match;
14718 {
14719     int i, length;
14720
14721     length = strlen(match);
14722
14723     for (i = strlen(string) - length; i >= 0; i--, string++)
14724       if (!strncmp(match, string, length))
14725         return string;
14726
14727     return NULL;
14728 }
14729
14730 char *
14731 StrCaseStr(string, match)
14732      char *string, *match;
14733 {
14734     int i, j, length;
14735
14736     length = strlen(match);
14737
14738     for (i = strlen(string) - length; i >= 0; i--, string++) {
14739         for (j = 0; j < length; j++) {
14740             if (ToLower(match[j]) != ToLower(string[j]))
14741               break;
14742         }
14743         if (j == length) return string;
14744     }
14745
14746     return NULL;
14747 }
14748
14749 #ifndef _amigados
14750 int
14751 StrCaseCmp(s1, s2)
14752      char *s1, *s2;
14753 {
14754     char c1, c2;
14755
14756     for (;;) {
14757         c1 = ToLower(*s1++);
14758         c2 = ToLower(*s2++);
14759         if (c1 > c2) return 1;
14760         if (c1 < c2) return -1;
14761         if (c1 == NULLCHAR) return 0;
14762     }
14763 }
14764
14765
14766 int
14767 ToLower(c)
14768      int c;
14769 {
14770     return isupper(c) ? tolower(c) : c;
14771 }
14772
14773
14774 int
14775 ToUpper(c)
14776      int c;
14777 {
14778     return islower(c) ? toupper(c) : c;
14779 }
14780 #endif /* !_amigados    */
14781
14782 char *
14783 StrSave(s)
14784      char *s;
14785 {
14786   char *ret;
14787
14788   if ((ret = (char *) malloc(strlen(s) + 1)))
14789     {
14790       safeStrCpy(ret, s, strlen(s)+1);
14791     }
14792   return ret;
14793 }
14794
14795 char *
14796 StrSavePtr(s, savePtr)
14797      char *s, **savePtr;
14798 {
14799     if (*savePtr) {
14800         free(*savePtr);
14801     }
14802     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14803       safeStrCpy(*savePtr, s, strlen(s)+1);
14804     }
14805     return(*savePtr);
14806 }
14807
14808 char *
14809 PGNDate()
14810 {
14811     time_t clock;
14812     struct tm *tm;
14813     char buf[MSG_SIZ];
14814
14815     clock = time((time_t *)NULL);
14816     tm = localtime(&clock);
14817     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14818             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14819     return StrSave(buf);
14820 }
14821
14822
14823 char *
14824 PositionToFEN(move, overrideCastling)
14825      int move;
14826      char *overrideCastling;
14827 {
14828     int i, j, fromX, fromY, toX, toY;
14829     int whiteToPlay;
14830     char buf[128];
14831     char *p, *q;
14832     int emptycount;
14833     ChessSquare piece;
14834
14835     whiteToPlay = (gameMode == EditPosition) ?
14836       !blackPlaysFirst : (move % 2 == 0);
14837     p = buf;
14838
14839     /* Piece placement data */
14840     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14841         emptycount = 0;
14842         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14843             if (boards[move][i][j] == EmptySquare) {
14844                 emptycount++;
14845             } else { ChessSquare piece = boards[move][i][j];
14846                 if (emptycount > 0) {
14847                     if(emptycount<10) /* [HGM] can be >= 10 */
14848                         *p++ = '0' + emptycount;
14849                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14850                     emptycount = 0;
14851                 }
14852                 if(PieceToChar(piece) == '+') {
14853                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14854                     *p++ = '+';
14855                     piece = (ChessSquare)(DEMOTED piece);
14856                 }
14857                 *p++ = PieceToChar(piece);
14858                 if(p[-1] == '~') {
14859                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14860                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14861                     *p++ = '~';
14862                 }
14863             }
14864         }
14865         if (emptycount > 0) {
14866             if(emptycount<10) /* [HGM] can be >= 10 */
14867                 *p++ = '0' + emptycount;
14868             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14869             emptycount = 0;
14870         }
14871         *p++ = '/';
14872     }
14873     *(p - 1) = ' ';
14874
14875     /* [HGM] print Crazyhouse or Shogi holdings */
14876     if( gameInfo.holdingsWidth ) {
14877         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14878         q = p;
14879         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14880             piece = boards[move][i][BOARD_WIDTH-1];
14881             if( piece != EmptySquare )
14882               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14883                   *p++ = PieceToChar(piece);
14884         }
14885         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14886             piece = boards[move][BOARD_HEIGHT-i-1][0];
14887             if( piece != EmptySquare )
14888               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14889                   *p++ = PieceToChar(piece);
14890         }
14891
14892         if( q == p ) *p++ = '-';
14893         *p++ = ']';
14894         *p++ = ' ';
14895     }
14896
14897     /* Active color */
14898     *p++ = whiteToPlay ? 'w' : 'b';
14899     *p++ = ' ';
14900
14901   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14902     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14903   } else {
14904   if(nrCastlingRights) {
14905      q = p;
14906      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14907        /* [HGM] write directly from rights */
14908            if(boards[move][CASTLING][2] != NoRights &&
14909               boards[move][CASTLING][0] != NoRights   )
14910                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14911            if(boards[move][CASTLING][2] != NoRights &&
14912               boards[move][CASTLING][1] != NoRights   )
14913                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14914            if(boards[move][CASTLING][5] != NoRights &&
14915               boards[move][CASTLING][3] != NoRights   )
14916                 *p++ = boards[move][CASTLING][3] + AAA;
14917            if(boards[move][CASTLING][5] != NoRights &&
14918               boards[move][CASTLING][4] != NoRights   )
14919                 *p++ = boards[move][CASTLING][4] + AAA;
14920      } else {
14921
14922         /* [HGM] write true castling rights */
14923         if( nrCastlingRights == 6 ) {
14924             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14925                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14926             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14927                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14928             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14929                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14930             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14931                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14932         }
14933      }
14934      if (q == p) *p++ = '-'; /* No castling rights */
14935      *p++ = ' ';
14936   }
14937
14938   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14939      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14940     /* En passant target square */
14941     if (move > backwardMostMove) {
14942         fromX = moveList[move - 1][0] - AAA;
14943         fromY = moveList[move - 1][1] - ONE;
14944         toX = moveList[move - 1][2] - AAA;
14945         toY = moveList[move - 1][3] - ONE;
14946         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14947             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14948             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14949             fromX == toX) {
14950             /* 2-square pawn move just happened */
14951             *p++ = toX + AAA;
14952             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14953         } else {
14954             *p++ = '-';
14955         }
14956     } else if(move == backwardMostMove) {
14957         // [HGM] perhaps we should always do it like this, and forget the above?
14958         if((signed char)boards[move][EP_STATUS] >= 0) {
14959             *p++ = boards[move][EP_STATUS] + AAA;
14960             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14961         } else {
14962             *p++ = '-';
14963         }
14964     } else {
14965         *p++ = '-';
14966     }
14967     *p++ = ' ';
14968   }
14969   }
14970
14971     /* [HGM] find reversible plies */
14972     {   int i = 0, j=move;
14973
14974         if (appData.debugMode) { int k;
14975             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14976             for(k=backwardMostMove; k<=forwardMostMove; k++)
14977                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14978
14979         }
14980
14981         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14982         if( j == backwardMostMove ) i += initialRulePlies;
14983         sprintf(p, "%d ", i);
14984         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14985     }
14986     /* Fullmove number */
14987     sprintf(p, "%d", (move / 2) + 1);
14988
14989     return StrSave(buf);
14990 }
14991
14992 Boolean
14993 ParseFEN(board, blackPlaysFirst, fen)
14994     Board board;
14995      int *blackPlaysFirst;
14996      char *fen;
14997 {
14998     int i, j;
14999     char *p, c;
15000     int emptycount;
15001     ChessSquare piece;
15002
15003     p = fen;
15004
15005     /* [HGM] by default clear Crazyhouse holdings, if present */
15006     if(gameInfo.holdingsWidth) {
15007        for(i=0; i<BOARD_HEIGHT; i++) {
15008            board[i][0]             = EmptySquare; /* black holdings */
15009            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15010            board[i][1]             = (ChessSquare) 0; /* black counts */
15011            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15012        }
15013     }
15014
15015     /* Piece placement data */
15016     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15017         j = 0;
15018         for (;;) {
15019             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15020                 if (*p == '/') p++;
15021                 emptycount = gameInfo.boardWidth - j;
15022                 while (emptycount--)
15023                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15024                 break;
15025 #if(BOARD_FILES >= 10)
15026             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15027                 p++; emptycount=10;
15028                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15029                 while (emptycount--)
15030                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15031 #endif
15032             } else if (isdigit(*p)) {
15033                 emptycount = *p++ - '0';
15034                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15035                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15036                 while (emptycount--)
15037                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15038             } else if (*p == '+' || isalpha(*p)) {
15039                 if (j >= gameInfo.boardWidth) return FALSE;
15040                 if(*p=='+') {
15041                     piece = CharToPiece(*++p);
15042                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15043                     piece = (ChessSquare) (PROMOTED piece ); p++;
15044                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15045                 } else piece = CharToPiece(*p++);
15046
15047                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15048                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15049                     piece = (ChessSquare) (PROMOTED piece);
15050                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15051                     p++;
15052                 }
15053                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15054             } else {
15055                 return FALSE;
15056             }
15057         }
15058     }
15059     while (*p == '/' || *p == ' ') p++;
15060
15061     /* [HGM] look for Crazyhouse holdings here */
15062     while(*p==' ') p++;
15063     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15064         if(*p == '[') p++;
15065         if(*p == '-' ) p++; /* empty holdings */ else {
15066             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15067             /* if we would allow FEN reading to set board size, we would   */
15068             /* have to add holdings and shift the board read so far here   */
15069             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15070                 p++;
15071                 if((int) piece >= (int) BlackPawn ) {
15072                     i = (int)piece - (int)BlackPawn;
15073                     i = PieceToNumber((ChessSquare)i);
15074                     if( i >= gameInfo.holdingsSize ) return FALSE;
15075                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15076                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15077                 } else {
15078                     i = (int)piece - (int)WhitePawn;
15079                     i = PieceToNumber((ChessSquare)i);
15080                     if( i >= gameInfo.holdingsSize ) return FALSE;
15081                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15082                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15083                 }
15084             }
15085         }
15086         if(*p == ']') p++;
15087     }
15088
15089     while(*p == ' ') p++;
15090
15091     /* Active color */
15092     c = *p++;
15093     if(appData.colorNickNames) {
15094       if( c == appData.colorNickNames[0] ) c = 'w'; else
15095       if( c == appData.colorNickNames[1] ) c = 'b';
15096     }
15097     switch (c) {
15098       case 'w':
15099         *blackPlaysFirst = FALSE;
15100         break;
15101       case 'b':
15102         *blackPlaysFirst = TRUE;
15103         break;
15104       default:
15105         return FALSE;
15106     }
15107
15108     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15109     /* return the extra info in global variiables             */
15110
15111     /* set defaults in case FEN is incomplete */
15112     board[EP_STATUS] = EP_UNKNOWN;
15113     for(i=0; i<nrCastlingRights; i++ ) {
15114         board[CASTLING][i] =
15115             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15116     }   /* assume possible unless obviously impossible */
15117     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15118     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15119     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15120                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15121     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15122     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15123     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15124                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15125     FENrulePlies = 0;
15126
15127     while(*p==' ') p++;
15128     if(nrCastlingRights) {
15129       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15130           /* castling indicator present, so default becomes no castlings */
15131           for(i=0; i<nrCastlingRights; i++ ) {
15132                  board[CASTLING][i] = NoRights;
15133           }
15134       }
15135       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15136              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15137              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15138              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15139         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15140
15141         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15142             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15143             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15144         }
15145         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15146             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15147         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15148                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15149         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15150                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15151         switch(c) {
15152           case'K':
15153               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15154               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15155               board[CASTLING][2] = whiteKingFile;
15156               break;
15157           case'Q':
15158               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15159               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15160               board[CASTLING][2] = whiteKingFile;
15161               break;
15162           case'k':
15163               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15164               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15165               board[CASTLING][5] = blackKingFile;
15166               break;
15167           case'q':
15168               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15169               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15170               board[CASTLING][5] = blackKingFile;
15171           case '-':
15172               break;
15173           default: /* FRC castlings */
15174               if(c >= 'a') { /* black rights */
15175                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15176                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15177                   if(i == BOARD_RGHT) break;
15178                   board[CASTLING][5] = i;
15179                   c -= AAA;
15180                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15181                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15182                   if(c > i)
15183                       board[CASTLING][3] = c;
15184                   else
15185                       board[CASTLING][4] = c;
15186               } else { /* white rights */
15187                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15188                     if(board[0][i] == WhiteKing) break;
15189                   if(i == BOARD_RGHT) break;
15190                   board[CASTLING][2] = i;
15191                   c -= AAA - 'a' + 'A';
15192                   if(board[0][c] >= WhiteKing) break;
15193                   if(c > i)
15194                       board[CASTLING][0] = c;
15195                   else
15196                       board[CASTLING][1] = c;
15197               }
15198         }
15199       }
15200       for(i=0; i<nrCastlingRights; i++)
15201         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15202     if (appData.debugMode) {
15203         fprintf(debugFP, "FEN castling rights:");
15204         for(i=0; i<nrCastlingRights; i++)
15205         fprintf(debugFP, " %d", board[CASTLING][i]);
15206         fprintf(debugFP, "\n");
15207     }
15208
15209       while(*p==' ') p++;
15210     }
15211
15212     /* read e.p. field in games that know e.p. capture */
15213     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15214        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15215       if(*p=='-') {
15216         p++; board[EP_STATUS] = EP_NONE;
15217       } else {
15218          char c = *p++ - AAA;
15219
15220          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15221          if(*p >= '0' && *p <='9') p++;
15222          board[EP_STATUS] = c;
15223       }
15224     }
15225
15226
15227     if(sscanf(p, "%d", &i) == 1) {
15228         FENrulePlies = i; /* 50-move ply counter */
15229         /* (The move number is still ignored)    */
15230     }
15231
15232     return TRUE;
15233 }
15234
15235 void
15236 EditPositionPasteFEN(char *fen)
15237 {
15238   if (fen != NULL) {
15239     Board initial_position;
15240
15241     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15242       DisplayError(_("Bad FEN position in clipboard"), 0);
15243       return ;
15244     } else {
15245       int savedBlackPlaysFirst = blackPlaysFirst;
15246       EditPositionEvent();
15247       blackPlaysFirst = savedBlackPlaysFirst;
15248       CopyBoard(boards[0], initial_position);
15249       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15250       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15251       DisplayBothClocks();
15252       DrawPosition(FALSE, boards[currentMove]);
15253     }
15254   }
15255 }
15256
15257 static char cseq[12] = "\\   ";
15258
15259 Boolean set_cont_sequence(char *new_seq)
15260 {
15261     int len;
15262     Boolean ret;
15263
15264     // handle bad attempts to set the sequence
15265         if (!new_seq)
15266                 return 0; // acceptable error - no debug
15267
15268     len = strlen(new_seq);
15269     ret = (len > 0) && (len < sizeof(cseq));
15270     if (ret)
15271       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15272     else if (appData.debugMode)
15273       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15274     return ret;
15275 }
15276
15277 /*
15278     reformat a source message so words don't cross the width boundary.  internal
15279     newlines are not removed.  returns the wrapped size (no null character unless
15280     included in source message).  If dest is NULL, only calculate the size required
15281     for the dest buffer.  lp argument indicats line position upon entry, and it's
15282     passed back upon exit.
15283 */
15284 int wrap(char *dest, char *src, int count, int width, int *lp)
15285 {
15286     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15287
15288     cseq_len = strlen(cseq);
15289     old_line = line = *lp;
15290     ansi = len = clen = 0;
15291
15292     for (i=0; i < count; i++)
15293     {
15294         if (src[i] == '\033')
15295             ansi = 1;
15296
15297         // if we hit the width, back up
15298         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15299         {
15300             // store i & len in case the word is too long
15301             old_i = i, old_len = len;
15302
15303             // find the end of the last word
15304             while (i && src[i] != ' ' && src[i] != '\n')
15305             {
15306                 i--;
15307                 len--;
15308             }
15309
15310             // word too long?  restore i & len before splitting it
15311             if ((old_i-i+clen) >= width)
15312             {
15313                 i = old_i;
15314                 len = old_len;
15315             }
15316
15317             // extra space?
15318             if (i && src[i-1] == ' ')
15319                 len--;
15320
15321             if (src[i] != ' ' && src[i] != '\n')
15322             {
15323                 i--;
15324                 if (len)
15325                     len--;
15326             }
15327
15328             // now append the newline and continuation sequence
15329             if (dest)
15330                 dest[len] = '\n';
15331             len++;
15332             if (dest)
15333                 strncpy(dest+len, cseq, cseq_len);
15334             len += cseq_len;
15335             line = cseq_len;
15336             clen = cseq_len;
15337             continue;
15338         }
15339
15340         if (dest)
15341             dest[len] = src[i];
15342         len++;
15343         if (!ansi)
15344             line++;
15345         if (src[i] == '\n')
15346             line = 0;
15347         if (src[i] == 'm')
15348             ansi = 0;
15349     }
15350     if (dest && appData.debugMode)
15351     {
15352         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15353             count, width, line, len, *lp);
15354         show_bytes(debugFP, src, count);
15355         fprintf(debugFP, "\ndest: ");
15356         show_bytes(debugFP, dest, len);
15357         fprintf(debugFP, "\n");
15358     }
15359     *lp = dest ? line : old_line;
15360
15361     return len;
15362 }
15363
15364 // [HGM] vari: routines for shelving variations
15365
15366 void
15367 PushTail(int firstMove, int lastMove)
15368 {
15369         int i, j, nrMoves = lastMove - firstMove;
15370
15371         if(appData.icsActive) { // only in local mode
15372                 forwardMostMove = currentMove; // mimic old ICS behavior
15373                 return;
15374         }
15375         if(storedGames >= MAX_VARIATIONS-1) return;
15376
15377         // push current tail of game on stack
15378         savedResult[storedGames] = gameInfo.result;
15379         savedDetails[storedGames] = gameInfo.resultDetails;
15380         gameInfo.resultDetails = NULL;
15381         savedFirst[storedGames] = firstMove;
15382         savedLast [storedGames] = lastMove;
15383         savedFramePtr[storedGames] = framePtr;
15384         framePtr -= nrMoves; // reserve space for the boards
15385         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15386             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15387             for(j=0; j<MOVE_LEN; j++)
15388                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15389             for(j=0; j<2*MOVE_LEN; j++)
15390                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15391             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15392             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15393             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15394             pvInfoList[firstMove+i-1].depth = 0;
15395             commentList[framePtr+i] = commentList[firstMove+i];
15396             commentList[firstMove+i] = NULL;
15397         }
15398
15399         storedGames++;
15400         forwardMostMove = firstMove; // truncate game so we can start variation
15401         if(storedGames == 1) GreyRevert(FALSE);
15402 }
15403
15404 Boolean
15405 PopTail(Boolean annotate)
15406 {
15407         int i, j, nrMoves;
15408         char buf[8000], moveBuf[20];
15409
15410         if(appData.icsActive) return FALSE; // only in local mode
15411         if(!storedGames) return FALSE; // sanity
15412         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15413
15414         storedGames--;
15415         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15416         nrMoves = savedLast[storedGames] - currentMove;
15417         if(annotate) {
15418                 int cnt = 10;
15419                 if(!WhiteOnMove(currentMove))
15420                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15421                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15422                 for(i=currentMove; i<forwardMostMove; i++) {
15423                         if(WhiteOnMove(i))
15424                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15425                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15426                         strcat(buf, moveBuf);
15427                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15428                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15429                 }
15430                 strcat(buf, ")");
15431         }
15432         for(i=1; i<=nrMoves; i++) { // copy last variation back
15433             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15434             for(j=0; j<MOVE_LEN; j++)
15435                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15436             for(j=0; j<2*MOVE_LEN; j++)
15437                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15438             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15439             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15440             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15441             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15442             commentList[currentMove+i] = commentList[framePtr+i];
15443             commentList[framePtr+i] = NULL;
15444         }
15445         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15446         framePtr = savedFramePtr[storedGames];
15447         gameInfo.result = savedResult[storedGames];
15448         if(gameInfo.resultDetails != NULL) {
15449             free(gameInfo.resultDetails);
15450       }
15451         gameInfo.resultDetails = savedDetails[storedGames];
15452         forwardMostMove = currentMove + nrMoves;
15453         if(storedGames == 0) GreyRevert(TRUE);
15454         return TRUE;
15455 }
15456
15457 void
15458 CleanupTail()
15459 {       // remove all shelved variations
15460         int i;
15461         for(i=0; i<storedGames; i++) {
15462             if(savedDetails[i])
15463                 free(savedDetails[i]);
15464             savedDetails[i] = NULL;
15465         }
15466         for(i=framePtr; i<MAX_MOVES; i++) {
15467                 if(commentList[i]) free(commentList[i]);
15468                 commentList[i] = NULL;
15469         }
15470         framePtr = MAX_MOVES-1;
15471         storedGames = 0;
15472 }
15473
15474 void
15475 LoadVariation(int index, char *text)
15476 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15477         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15478         int level = 0, move;
15479
15480         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15481         // first find outermost bracketing variation
15482         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15483             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15484                 if(*p == '{') wait = '}'; else
15485                 if(*p == '[') wait = ']'; else
15486                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15487                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15488             }
15489             if(*p == wait) wait = NULLCHAR; // closing ]} found
15490             p++;
15491         }
15492         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15493         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15494         end[1] = NULLCHAR; // clip off comment beyond variation
15495         ToNrEvent(currentMove-1);
15496         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15497         // kludge: use ParsePV() to append variation to game
15498         move = currentMove;
15499         ParsePV(start, TRUE);
15500         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15501         ClearPremoveHighlights();
15502         CommentPopDown();
15503         ToNrEvent(currentMove+1);
15504 }
15505